从输出可以看出,当要执行blk的时候,test()已经执行完毕回到main函数中,对应的函数栈也已经展开,此时栈上的变量已经不存在了,继续访问导致崩溃——这也是不用int *直接访问外部变量i的原因。
5.1 拷贝block结构体
上文提到block结构体__block_impl的第一个成员是isa指针,使其成为NSObject的子类,所以我们可以通过相应的内存管理机制将其拷贝到堆上:
[cpp]
void test()
{
__block int i = 1024;
void(^blk)(void) = ^{ i = 2048; printf("%d\n", i); };
pthread_tthread;
intret = pthread_create(&thread, NULL, testBlock, (void*)[blk copy]);
printf("thread returns : %d\n", ret);
sleep(1);
}
void*testBlock(void*blk)
{
sleep(2);
printf("testBlock : Begin to exec blk.\n");
DemoBlock demoBlk = (DemoBlock)blk;
demoBlk();
[demoBlk release];
return NULL;
}
再次执行,得到输出:
[cpp]
Before test()
threadreturns : 0
After test()
testBlock : Begin to exec blk.
2048
可以看出,在test()函数栈展开后,demoBlk仍然可以成功执行,这是由于blk对应的block结构体__main_block_impl_0已经在堆上了。不过这还不够——
5.2 拷贝捕获的变量(__block变量)
在拷贝block结构体的同时,还会将捕获的__block变量,即结构体__Block_byref_i_0,复制到堆上。这个任务落在前面没有讨论的__main_block_desc_0结构体身上:
[cpp]
static void __main_block_copy_0(struct__main_block_impl_0*dst, struct__main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct__main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __main_block_desc_0 {
unsignedlongreserved;
unsignedlongBlock_size;
void(*copy)(struct__main_block_impl_0*, struct__main_block_impl_0*);
void(*dispose)(struct__main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct__main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
栈上的__main_block_impl_0结构体为src,堆上的__main_block_impl_0结构体为dst,当发生复制动作时,__main_block_copy_0函数会得到调用,将src的成员变量i,即__Block_byref_i_0结构体,也复制到堆上。
5.3 __forwarding指针的作用
当复制动作完成后,栈上和堆上都存在着__main_block_impl_0结构体。如果栈上、堆上的block结构体都对捕获的外部变量进行操作,会如何?
下面是一段示例代码:
[cpp]
void test()
{
__block int i = 1024;
void(^blk)(void) = ^{ i++; printf("%d\n", i); };
pthread_tthread;
intret = pthread_create(&thread, NULL, testBlock, (void*)[blk copy]);
printf("thread returns : %d\n", ret);
sleep(1);
blk();
}
void *testBlock(void*blk)
{
sleep(2);
printf("testBlock : Begin to exec blk.\n");
DemoBlock demoBlk = (DemoBlock)blk;
demoBlk();
[demoBlk release];
returnNULL;
}
在test()函数中调用pthread_create创建线程时,blk被复制了一份到堆上作为testBlock函数的参数。
test()函数中的blk结构体位于栈中,在休眠1s后被执行,对i进行自增动作。
testBlock函数在休眠2s后,执行位于堆上的block结构体,这里为demoBlk。
上述代码执行后输出:
[cpp]
Beforetest()
thread returns : 0
1025
Aftertest()
testBlock : Begin to execblk.
1026
可见无论是栈上的还是堆上的block结构体,修改的都是同一个__block变量。
这就是前面提到的__forwarding指针成员的作用了:
起初,栈上的__block变量的成员指针__forwarding指向__block变量本身,即栈上的__Block_byref_i_0结构体。
当__block变量被复制到堆上后,栈上的__block变量的__forwarding成员会指向堆上的那一份拷贝,从而保持一致。