block实质

block定义struct Block_descriptor {
    unsigned long int reserved;
    unsigned long int size;
    void (*copy)(void *dst, void *src);
    void (*dispose)(void *);
};

struct Block_layout {
    void *isa;
    int flags;
    int reserved; 
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};

实际上block就是这俩玩意了

我们来举个栗子看看


定义一个最简单block 打印hello world

使用clang指令
clang -rewrite-objc main.m
得到一个cpp文件
打开之,你就会看到什么是block了


cpp文件

你定义完block之后,其实是创建了一个函数,在创建结构体的时候把函数的指针一起传给了block,所以之后可以拿出来调用。

再看看值捕获的问题


未加__block前缀

未加__block前缀

定义block的时候,变量a的值就传递到了block结构体中,仅仅是值传递,所以在block中修改a是不会影响到外面的a变量的。


加了__block前缀

加了__block前缀

并不是直接传递a的值了,而是把a的地址传过去了,所以在block内部便可以修改到外面的变量了。

根据isa指针,block一共有3种类型的block
_NSConcreteGlobalBlock 全局静态
_NSConcreteStackBlock 保存在栈中,出函数作用域就销毁
_NSConcreteMallocBlock 保存在堆中,retainCount == 0销毁

而ARC和MRC中,还略有不同:

在 ARC 中,捕获了外部变量的 block 的类会是 _NSConcreteMallocBlock 或者 _NSConcreteStackBlock,如果 block 被赋值给了某个变量,在这个过程中会执行 __Block__copy 将原有的 _NSConcreteStackBlock 变成 _NSConcreteMallocBlock ;但是如果 block没有赋值给某个变量,那他的类型就是 _NSConcreteStackBlock ;没有捕获外部变量的 block 的类会是 _NSConcreteGlobalBlock 。即不在堆上,也不在栈上,它类似 C 语言函数一样会在代码段中。

在非 MRC 中,捕获了外部变量的 block 的类会是 _NSConcreteStackBlock ,放在栈上,没有捕获外部变量的 block 时与 ARC 环境下情况相同。

补充:
在MRC下NSStackBlock需要进行copy才会被拷贝到堆区,否则调用block不在原来的方法堆栈时,会出现被释放的情况,其强引用的对象也可能会因为无引用而被释放;

随后,在ARC中,苹果对NSStackBlock进行了自动处理,当将NSStackBlock给另一个对象赋值时,会自动将block从栈区拷贝到堆区,这样就避免了上面的问题;但是将block当作参数传递给其他方法时,并不会自动将block从栈区拷贝到堆区,而是只有在方法中异步引用了block时,才会自动将block从栈区拷贝到堆区。

比如 [self addBlock:block]; //block是NSStackBlock

-(void)addBlock:(void(^)(void))block{

      NSLog(@"%@",block);

}

这时并不会自动将block从栈区拷贝到堆区,因为没有被异步引用时,block一直跟方法调用在同一个调用堆栈里,并不会出现被释放掉的情况;

但是按下面这种写法,

-(void)addBlock:(void(^)(void))block{

   NSLog(@"%@",block);  

   dispatch_after(dispatch_time(DISPATCH_TIME_NOW,

                                          (int64_t)(2 * NSEC_PER_SEC)),          

                                       dispatch_get_global_queue(0, 0), ^{
          block(nil,nil);
   });

}

这时会自动将block从栈区拷贝到堆区,因为block被异步引用了,有可能会出现跟方法调用时不处于同一个调用堆栈,有可能会被释放掉;

苹果这么处理也是充分考虑到了性能问题,在需要进行拷贝时才进行拷贝,减少不必要的资源浪费;

链接:https://www.zhihu.com/question/30779258/answer/49492783
链接:https://www.jianshu.com/p/08010a3ee59e

你可能感兴趣的:(block实质)