Block是什么?
一般来说OC上的语法糖,都可以使用 clang --rewrite-objc code.m 让clang 编译为 c 或 c++ ,由此可以窥探这些语法糖的具体实现。例如,oc的block,@autoreleasepool,@synchronized等等
说明
- Block的本质是个C的Struct,所以可以像C的Struct一样使用。
1
2
3
4typedef int (^blk_t) (int)
blk_t blk = ^(int count){return count;};
blk_t *blkptr = &blk;
(*blkptr)(10); - Block 没有实现截获C语言数组,只能通过指针截获。
1
2
3
4
5//const char text[] = “hello”;编译失败
const char *text = “hello”;
void (^blk)(void) = ^{
text[0];
};
实验&实现
本文用一些实验,来通过实验现象来了解Block的具体实现
没有任何截获
源码:
clang 重写之后:
可以看到Block的在C语言的实现中是一个struct。struct中记录了Block具体方法实现的imp(即void *FuncPtr)。在调用Block的时候,实际上是调用了这个struct(__block_impl)内存储的函数指针。
截获基本数据类型的自动变量
源码:
clang 重写之后:
如果在Block内截获了外部的基本数据类型,那么这个基本数据类型就会在Block初始化的时候,以当时的“快照”被存储到Block结构体的成员变量内。在调用void *FuncPtr的时候,方法内部使用的数据就是这些被截获的数据。
为什么不支持C语言数组??
1 | //如果支持C语言数组,那么必然会出现如下代码 |
截获全局变量 && 静态全局变量 && 静态变量
源码:
clang 重写之后:
全局变量和静态全局变量没有什么区别,重写之后访问这些变量的方式依旧不变。
不同的是静态变量,静态变量在Block内会被截获这个静态变量的地址。如果是普通变量,在作用域结束的时候就会随着函数的结束,栈的弹出而销毁。但是静态变量是在程序执行前就分配好的数据区,并不会随着函数作用域而释放,所以Block可以通过截获这个变量的地址来访问这个变量。
截获对象(指针)类型的自动变量
源码:
clang 重写之后:
在Block被copy的时候调用desc0中的__main_block_copy_0持有OC对象
在Block被release的时候调用desc0中的__main_block_dispose_0释放持有的OC对象
截获__block修饰的基本数据类型
源码:
clang 重写之后:
变量 i 变成了一个结构体对象__Block_byref_val_0,并且block_imp中持有的也是这个结构体指针
截获__block修饰的指针类型的自动变量
源码:
clang 重写之后:
__Block_byref 对象新增Copy函数指针和dispose函数指针
__Block_byref 也是一个对象,所以在BlockCopy的时候会调用__Block_byref的copy,同样释放的时候也会调用dispose
注:在ARC无效的时候,byref不会调用对象的retain。(可以被用来在MRC下避免循环引用)
QA.
__block变量超出变量作用域存在的理由?
根据上面带__block变量基本类型变量和指针类型变量的描述,可以解释这个问题
forwarding存在的理由?
这张图来自于(Objective-C高级编程 iOS与OS X多线程和内存管理)
栈上的__block变量的结构体实例,在__block变量从栈上复制到堆上的时候(__Block_byref_id_object_copy_131参数中一个是堆上的,一个是栈上的),会将成员变量__forwarding的值替换为堆上的值。
当Block被Copy的时候就会Copy __Block_byref,并且改变原有 __forwarding 指针
Block的存储域?
- 在 ARC 环境下,只要给一个自动变量赋值一个堆栈
__NSStackBlock__上block,那么就会调用block的copy - 如果Block不截获任何变量那么生成
__NSGlobalBlock__存储在数据区 - GCD的参数block和Cocoa框架中的UsingBlock传入的参数会被copy
__NSStackBlock__会被copy到__NSMallocBlock____NSMallocBlock__copy会增加引用计数__NSGlobalBlock__copy没有任何影响
为什么说Block是Objc对象?
- block 的结构体可以直接转换为oc对象 objc_object的结构体。objc_object中的isa指向的是class,同样的Block也是指向
__NSMallocBlock__、__NSMallocBlock__、__NSMallocBlock__class,这是三个class在runtime初始化的时候会被注册到class表中。 - block 和 object在同样的内存管理机制下进行创建和释放。