Block数据结构
struct __ArcClass__TestArc_block_impl_0 {
struct __block_impl impl;
struct __ArcClass__TestArc_block_desc_0* Desc;
int age;
__ArcClass__TestArc_block_impl_0(void *fp, struct __ArcClass__TestArc_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
- struct __block_impl impl
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
定义一个block的时候,底层生成了一个代表block的结构体__ArcClass__TestArc_block_impl_0,该结构体有一个__block_impl类型的impl成员变量和代表捕获变量的成员变量。其中impl的isa 代表了block的类型,FuncPtr代表了block的实际调用方法,该方法的参数为__ArcClass__TestArc_block_impl_0。
截获变量
- static修饰的变量, 在block内可以修改变量的值,因为在底层block捕获的是地址
- 全局变量可以直接在block中修改值,block不会捕获全局变量, 而是直接使用, 所以可以直接改值
- auto变量需要__block修饰才能修改
Block的内存管理
Block的类型
Block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型 NSGlobalBlock 全局 block 没有访问auto变量 NSStackBlock 栈 block 访问了auto变量 NSMallocBlock 堆 block __NSStackBlock__调用了copy
Block copy
在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况
- block作为函数返回值时
- 将block赋值给__strong指针时
- block作为Cocoa API中方法名含有usingBlock的方法参数时
- block作为GCD API的方法参数时
Block 变量的内存管理
- 基本数据类型的auto变量,当栈上block复制到堆上时, 会直接
将捕获的基本数据类型变量复制到堆中 - 对象类型的auto变量,当变量被强引用修饰时, block复制到堆上的过程中会调用copy函数, copy函数内部会调用里面的_Block_object_assign函数,
对被捕获的对象变量进行强引用 - 对象类型的auto变量,当对象变量被__weak修饰时, block从栈中复制到堆中, 依然会调用copy函数, copy函数内部会调用里面的_Block_object_assign函数,只不过
不会再对被__weak修饰的变量进行强引用 - __block修饰的auto变量,在底层会被包装成一个__Block_byref_age_0对象,当block从栈上复制到堆上时, 就会调用__main_block_desc_0中的copy函数, 对__Block_byref_age_0对象
进行强引用,移除时会调用dispose函数,移除强引用。
Block的循环引用
- __weak: 当person被释放时, block中的__weak person会指向nil
- __unsafe_unretained: 当person被释放时, block中的__unsafe_unretained person不会指向nil, 造成野指针
- __block: 手动设置nil
__block修饰符
可以用于解决block内部无法修改auto变量值的问题,编译器会将block变量包装成一个对象。__block不能修饰全局变量、静态变量。
__block结构
对于__block变量,底层会封装成一个对象,其中通过__forwarding指向自己,来访问真实的变量。
__forwarding指针
- 当block在栈上时, 通过__forwarding指针拿到的是
栈中的__block结构体 - 当block在堆上时, 通过__forwarding指针拿到的是
堆中的__block结构体
为什么要通过__forwarding访问?
这是因为,如果__block变量在栈上,就可以直接访问,但是如果已经拷贝到了堆上,访问的时候,还去访问栈上的,就会出问题,所以,先根据__forwarding找到堆上的地址,然后再取值
__block内存管理
-
当Block在栈上时,并不会对__block变量产生强引用。
-
当Block被copy到堆时,会调用Block内部的copy函数,copy函数内部会调用_Block_object_assign函数, _Block_object_assign函数会对__block变量形成强引用(retain)。
-
当Block从堆中移除时,会调用Block内部的dispose函数,dispose函数内部会调用_Block_object_dispose函数\,_Block_object_dispose函数会自动释放引用的__block变量(release)