Objective-C:ブロックの仕組み

この記事では、ブロックの位置(__NSStackBlock __ / __ NSGlobalBlock __ / __ NSMallocBlock__)、変数のキャプチャ方法、およびブロックのコンパイル先との関係について説明します。



現時点では、Objective-Cでのブロックの使用は、この言語を学習した最初の数日からほぼ始まります。 しかし、ほとんどの場合、開発者はブロックが内部でどのように機能するかを考えません。 魔法はありません。それについて詳しく説明します。



Objective-Cでブロックがどのように見えるかを最初から始めましょう









使用するブロックは何ですか、私はペイントしません、これはそれについてではないので、すぐに実際の興味深い点を見てみましょう。



ブロックとは何ですか? まず、ブロックはオブジェクトです。

id thisIsBlock = ^{ };
      
      





理解するために、オブジェクトとは何かを検討してください。

 struct objc_object { Class isa OBJC_ISA_AVAILABILITY; }; /// A pointer to an instance of a class. typedef struct objc_object *id;
      
      





ブロックにはメソッドがあります

 - (Class)class
      
      



isaを返します



私たちが今知っているものを使用して、ブロックがどのような状況にあるかを見ていきます

__NSStackBlock__
  int foo = 3; Class class = [^{ int foo1 = foo + 1; } class]; NSLog(@"%@", NSStringFromClass(class));
      
      





2015-11-29 22:30:21.054 block_testing [99727:13189641] __NSStackBlock__



__NSMallocBlock__
  int foo = 3; Class class = [[^{ int foo1 = foo + 1; } copy] class]; NSLog(@"%@", NSStringFromClass(class));
      
      





2015-11-29 22:33:45.026 block_testing [99735:13190778] __NSMallocBlock__



__NSGlobalBlock__
  Class class = [^{ } class]; NSLog(@"%@", NSStringFromClass(class));
      
      





2015-11-29 22:34:49.645 block_testing [99743:13191389] __NSGlobalBlock__





ただし、別のオプション__NSMallocBlock__を検討してください

ARC
  int foo = 3; id thisIsBlock = ^{ int foo1 = foo + 1; }; Class class = [thisIsBlock class]; NSLog(@"%@", NSStringFromClass(class));
      
      





2015-11-29 22:37:27.638 block_testing [99751:13192462] __NSMallocBlock__



ご覧のとおり、ブロックが外部変数をキャプチャしない場合、__ NSGlobalBlock__を取得します

実験
  Class class = [[^{ } copy] class]; NSLog(@"%@", NSStringFromClass(class));
      
      





  id thisIsBlock = ^{ }; Class class = [thisIsBlock class]; NSLog(@"%@", NSStringFromClass(class));
      
      





__NSGlobalBlock__





ブロックが外部変数をキャプチャする場合、__ NSStackBlock__ブロック(スタック上)。 ただし、ブロック「コピー」を送信すると、ブロックはヒープ(__NSMallocBlock__)にコピーされます。



ARCはこれを支援し、変数(__strong)にブロックを割り当てると、ブロックはヒープにコピーされます。これは上記の例で確認できます。 一般的に、ARCに再び感謝します。MRCでは非常に不快なバグが発生する可能性があるためです。

object.h
 /*! * @typedef dispatch_block_t * * @abstract * The type of blocks submitted to dispatch queues, which take no arguments * and have no return value. * * @discussion * When not building with Objective-C ARC, a block object allocated on or * copied to the heap must be released with a -[release] message or the * Block_release() function. * * The declaration of a block literal allocates storage on the stack. * Therefore, this is an invalid construct: * <code> * dispatch_block_t block; * if (x) { * block = ^{ printf("true\n"); }; * } else { * block = ^{ printf("false\n"); }; * } * block(); // unsafe!!! * </code> * * What is happening behind the scenes: * <code> * if (x) { * struct Block __tmp_1 = ...; // setup details * block = &__tmp_1; * } else { * struct Block __tmp_2 = ...; // setup details * block = &__tmp_2; * } * </code> * * As the example demonstrates, the address of a stack variable is escaping the * scope in which it is allocated. That is a classic C bug. * * Instead, the block literal must be copied to the heap with the Block_copy() * function or by sending it a -[copy] message. */ typedef void (^dispatch_block_t)(void);
      
      









なぜ財産は「コピー」として指定されるのですか?

注:元のスコープ外のキャプチャされた状態を追跡するにはブロックをコピーする必要があるため、プロパティ属性としてコピーを指定する必要があります。 これは、自動参照カウントを使用する場合、自動的に行われるため、心配する必要はありませんが、プロパティ属性が結果の​​動作を表示するためのベストプラクティスです。 詳細については、ブロックプログラミングのトピックを参照してください。

参照

したがって、ブロックのプロパティコピーの書き込みを続けます。



__NSStackBlock__に関する別の小さな例を検討してください

__NSStackBlock__を関数に渡すとどうなりますか?
 typedef void(^EmptyBlock)(); void fooFunc(EmptyBlock block) { Class class = [block class]; NSLog(@"%@", NSStringFromClass(class)); } int main(int argc, const char * argv[]) { @autoreleasepool { int foo = 1; fooFunc(^{ int foo1 = foo + 1; }); } return 0; }
      
      





2015-11-29 22:52:16.905 block_testing [99800:13197825] __NSStackBlock__





__NSGlobalBlock__が必要な理由
__NSStackBlock__と__NSMallocBlock__については多くの情報がありましたが、__ NSGlobalBlock__の使用法は何ですか? このようなブロック内では、外部変数のキャプチャはありません。つまり、__ NSStackBlock__を使用する場合のように、メモリの可視性が原因で違反が発生することはありません。 また、最初にスタック上にブロックを作成し、必要に応じてブロックをヒープに転送する必要がないため、この状況をはるかに簡単に解決できます。





この記事では、外部変数のキャプチャについて何度も述べていますが、これがどのように起こるかについてはまだ述べていません。 clangのドキュメントはこれに役立ちます。 完全にペイントするのではなく、必要なアイデアのみを選択します。



ブロックは構造に変わり、グリップはフィールドに変わります。



実際に確認するのは簡単です。

目利きのための脚注
clang -rewrite-objc -ObjC main.m -o out.cpp


C ++コードでプロセス全体を見ることができます



  MyObject *myObject = [[MyObject alloc] init]; NSLog(@"object %p, ptr myObject %p", myObject, &myObject); ^{ NSLog(@"object %p, ptr myObject %p", myObject, &myObject); }();
      
      





2015-11-29 23:12:37.297 block_testing [99850:13203592]オブジェクト0x100111e10、ptr myObject 0x7fff5fbff798

2015-11-29 23:12:37.298 block_testing [99850:13203592]オブジェクト0x100111e10、ptr myObject 0x7fff5fbff790



ご覧のとおり、ブロックの内側と外側のmyObjectポインターは同じメモリ領域を指しますが、ポインター自体は異なります。



ブロックは、定数ローカル変数と、それ自体がキャプチャするもののポインターを作成します。 オブジェクトを使用すると、ARCの通常のロジック(ブロックをヒープにコピーする場合)に従って、参照カウンターが増加します。



ただし、__ blockを使用すると、inoutが発生するため、参照カウントは増加しません(ただし、常にどこでも使用しないでください。constは良い言葉です)。



統合するには、小さな例を検討してください

コード
 #import <Foundation/Foundation.h> typedef NSInteger (^IncrementBlock)(); IncrementBlock createIncrementBlock(const NSInteger start, const NSInteger incrementValue) { __block NSInteger acc = start; return ^NSInteger{ acc += incrementValue; return acc; }; } int main(int argc, const char * argv[]) { @autoreleasepool { IncrementBlock incrementBlock = createIncrementBlock(0, 2); NSLog(@"%ld", incrementBlock()); NSLog(@"%ld", incrementBlock()); NSLog(@"%ld", incrementBlock()); IncrementBlock incrementBlock1 = createIncrementBlock(0, 2); NSLog(@"%ld", incrementBlock1()); NSLog(@"%ld", incrementBlock1()); NSLog(@"%ld", incrementBlock1()); } return 0; }
      
      





2015-11-29 23:31:24.027 block_testing [99910:13209611] 2

2015-11-29 23:31:24.028 block_testing [99910:13209611] 4

2015-11-29 23:31:24.028 block_testing [99910:13209611] 6

2015-11-29 23:31:24.028 block_testing [99910:13209611] 2

2015-11-29 23:31:24.028 block_testing [99910:13209611] 4

2015-11-29 23:31:24.029 block_testing [99910:13209611] 6



IncrementBlockブロックはコンパイラーによって構造に変換され、ブロックが関数から返されると、現在の領域がコピーされ、それによってバッテリーが格納されるフィールドを持つ構造が得られました。 また、createIncrementBlock関数を呼び出すたびに、新しいインスタンスが取得されます。



また、ブロック内でのselfの使用(self とは何か )にも焦点を当てます。 この記事を読んだ後、__ NSMallocBlock__内でselfを使用すると参照カウントが増加することが明らかになりますが、保持サイクルを意味するものではありません。 保持サイクルは、オブジェクトがブロックを保持し、ブロックがブロックを保持するオブジェクトを保持するときです...



パラノイアは、どこでも__weak __strongを使用する必要はありません。私たちにとってブロックはオブジェクトです。 保存、転送、使用できるオブジェクトで、通常のメモリ管理のルールが適用されます。



All Articles