不知道大家使用block的时候有没有想过:
为什么block赋值需要用copy?
Blk blockTest0 = ^() {
printf("assa\n");
};
Blk blockTest = [blockTest0 copy];
对于全局block来说,就和全局常量一样,copy就是引用,且没有引用计数的增减,在app结束前不会释放,对于这类block就相当于一个无参数的c函数,我们只需要一个函数指针来调用它,blockTest就是它的函数指针。
__block int h = 9;
printf("h在block外的地址%p\n",&h);
//__NSStackBlock__,直接在栈中被调用
void(^staBlock1)(void) = ^{
printf("h在block内的地址%p\n",&h);
h = 11;
printf("h在block内赋值后的地址%p\n",&h);
}();
对于上面这个block,我们先不考虑__block的存在,我们引用了外部变量h,h是在block外是局部变量,存储在栈中,栈内存由系统控制释放,当我们在调用staBlock1的时候,可能变量h已经在栈中被释放了,这导致后续调用h引发程序崩溃;
当block有赋值操作时,这个block就可能在离开这个作用域后被调用,为了避免这种情况,OC采用了copy方法,
struct __ClangProxy__staff_block_impl_8 {
struct __block_impl impl;
struct __ClangProxy__staff_block_desc_8* Desc;
. . .;//引用的外部变量
__ClangProxy__staff_block_impl_8(void *fp, struct __ClangProxy__staff_block_desc_8 *desc, int *_tt, int flags=0) : tt(_tt) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
由于对外部变量的引用是存放在block的结构体中的,只要将这个结构体重新copy一份,放到堆上,引用堆上的变量(堆上变量根据引用计数来管理释放与否),也就不会有“调用已被释放的变量”这种情况。
为什么在block内有变值操作的变量,需要使用__block修饰?
copy问题解决了“调用已被释放的变量”的问题,但是另一个问题接踵而来,
在上面代码中,block使用copy方法后,变量h被copy到了堆中,那么它就与原来的h互不相干了,此时在block对h赋值h=11,就是堆上的h变为11,而block外部的h是存储在栈中的,它还是9,这与编程意图有悖,这个编程意图很显然是要同时改变block内外h的值,保持block内外变量的一致性才是编程的要求。
这个问题,OC使用了__block
来解决了,使用这个 关键字修饰 会将h从一个基本类型int变量,变换为一个结构体的成员变量,例如:
struct __Block_byref_h_0 {
void *__isa;
__Block_byref_h_0 *__forwarding;
int __flags;
int __size;
int h;
};
这个结构体我在关于block的clang源码中有讲到。使用__block后,block结构体中引用的就不是基本类型h了,而是结构体__Block_byref_h_0 *struct_h;
,在block外,当还没有发生block的copy操作时,__Block_byref_h_0 *struct_h
存储在栈中,结构体中还有一个成员__Block_byref_h_0 *__forwarding
是指向自身的指针,再看使用h的时候,我们的调用方法是struct_h->__forwarding->h
,这并不是多此一举;在block发生copy操作后,__Block_byref_h_0 *struct_h
结构体会被copy到堆中__Block_byref_h_0 *malloc_struct_h
,这时__forwarding就发挥它的作用了,它指向了堆中的结构体__forwarding = malloc_struct_h,我们调用h的时候相当于是struct_h->malloc_struct_h->h
,这里也就解决了变量copy到堆后,block赋值导致变量在block内外值不一样的问题了。
网友评论