厄介な詳細は、ブロック実装仕様にあります。
例を挙げて説明するのが最も簡単です。単純なブロックを含むこの単純な関数について考えてみます。
void outerFunction() {
int x = 7;
dispatch_block_t block = ^{
printf("%d\n", x);
};
dispatch_sync(dispatch_get_main_queue(), block);
}
dispatch_block_t
これはのtypedefであることを忘れないでくださいvoid (^)(void)
。
そのコードをコンパイルするために、コンパイラーは最初に2つの構造体定義を作成します。
struct Block_descriptor_1 {
unsigned long reserved;
unsigned long size;
const char *signature;
};
struct Block_literal_1 {
void *isa;
int flags;
int reserved;
void (*invoke)(void *);
struct Block_descriptor_1 *descriptor;
int x;
};
次に、タイプのグローバル変数を作成します。これには、ブロックのタイプシグネチャのBlock_descriptor_1
サイズとエンコーディングが含まれます。Block_literal_1
struct Block_descriptor_1 block_descriptor_1 = {
.size = sizeof(struct Block_literal_1),
.signature = "v4@?0"
};
そして、ブロックの本体を含む関数を作成します。
void outerFunction_block_invoke(void *voidLiteral) {
struct Block_literal_1 *literal = (struct Block_literal_1 *)voidLiteral;
printf("%d\n", literal->x);
}
ブロックの本体が書き直さx
れ、囲んでいるスコープからの変数へのアクセスがブロックリテラルのメンバーへのアクセスになることに注意してください。
最後に、コンパイラは元の関数を書き直して、スタック上にブロックリテラルを作成し、ブロックの代わりにそのブロックリテラルのアドレスを使用します。
void outerFunction2() {
int x = 7;
struct Block_literal_1 block_literal_1 = {
.isa = __NSConcreteStackBlock,
.flags = BLOCK_HAS_SIGNATURE,
.invoke = outerFunction_block_invoke,
.descriptor = &block_descriptor_1,
.x = x
};
dispatch_sync(dispatch_get_main_queue(),
(__bridge dispatch_block_t)&block_literal_1);
}
ブロックリテラルは、特別なObjective-Cへのポインタで始まることに注意してくださいClass
。これにより、ブロックリテラルをObjective-Cオブジェクトとして扱うことができます。
__block
属性をローカル変数に追加するとx
、より複雑になります。その場合、コンパイラは、変数に関する情報(変数がオブジェクトへのポインタである場合に変数を保持および解放する方法など)とともにその変数を保持するための別の構造を作成する必要があります。これはすべて、この回答の上部にリンクした仕様で説明されています。