关于block(二)----为什么使用copy,为什么使用__block

不知道大家使用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内外值不一样的问题了。

你可能感兴趣的:(关于block(二)----为什么使用copy,为什么使用__block)