1. Block类型
在日常开发中,Block分为三种类型:
GlobalBlock位于全局区
在
Block内部不能捕获外部变量,或只使用静态变量或全局变量
MallocBlock位于堆区
在
Block内部使用局部变量或OC属性,并且赋值给强引用或Copy修饰的变量
StackBlock位于栈区
与
MallocBlock一样,可以在内部使用局部变量或OC属性。但不能赋值给强引用或Copy修饰的变量
1.1 GlobalBlock
全局Block,不能捕获外部变量,但可以使用静态变量或全局变量
int age;- (void)viewDidLoad {[super viewDidLoad];static int number;void (^block)(void) = ^{int a = 0;NSLog(@"%d - %d - %d", number, age, a);};NSLog(@"%@",block);}-------------------------//输出以下内容:<__NSGlobalBlock__: 0x1047900f8>
- 使用静态变量、全局变量、
Block内部声明的变量,都没有问题
1.2 MallocBlock
堆区Block,内部使用局部变量或OC属性,并且赋值给强引用或Copy修饰的变量
- (void)viewDidLoad {[super viewDidLoad];int number = 0;void (^block)(void) = ^{NSLog(@"%d", number);};NSLog(@"%@",block);}-------------------------//输出以下内容:<__NSMallocBlock__: 0x281487930>
- 对外部的
number变量进行捕获 - 赋值给强引用的
block - 其中
block持有的是堆区Block的内存地址
1.3 StackBlock
栈区Block,和堆区Block的使用大致相同,区别在于不能赋值给强引用或Copy修饰的变量
- (void)viewDidLoad {[super viewDidLoad];int number = 0;void (^__weak block)(void) = ^{NSLog(@"%d", number);};NSLog(@"%@",block);}-------------------------//输出以下内容:<__NSStackBlock__: 0x16b5e0f68>
- 同样对外部的
number变量进行捕获 - 和堆区
Block的区别:赋值给弱引用的block
2. Block案例
2.1 Block的内存拷贝
- (void)blockDemo{int a = 0;void(^__weak weakBlock)(void) = ^{NSLog(@"a:%d", a);};struct _LGBlock *blc = (__bridge struct _LGBlock *)weakBlock;id __strong strongBlock = weakBlock;NSLog(@"weakBlock:%@",weakBlock);NSLog(@"strongBlock:%@",strongBlock);blc->invoke = nil;void(^strongBlock1)(void) = strongBlock;NSLog(@"strongBlock1:%@",strongBlock1);strongBlock1();}-------------------------//输出以下内容:weakBlock:<__NSStackBlock__: 0x16f6b4f48>strongBlock:<__NSStackBlock__: 0x16f6b4f48>strongBlock1:<__NSMallocBlock__: 0x283a48ff0>程序闪退,坏地址访问(EXC_BAD_ACCESS)
代码解读:
第一步,
weakBlock为栈区Block第二步,按
Block的底层源码,自定义_LGBlock结构体。只要内存结构一致,即可将Block桥接为自定义对象第三步,
Block的本质是结构体,将结构体首地址赋值给__strong修饰的对象第四步,将结构体的
invoke置空,即:Block的函数指针第五步,将
strongBlock赋值给强引用的strongBlock1,然后对其进行调用
闪退原因:
在第三步中,将结构体首地址赋值给对象,二者指向相同内存空间
在第四步中,将结构体的
invoke置空,修改的是同一片内存空间在第五步中,将
invoke置空后的Block赋值给strongBlock1,调用时坏地址访问,程序闪退
修改案例:
- (void)blockDemo{int a = 0;void(^__weak weakBlock)(void) = ^{NSLog(@"a:%d", a);};struct _LGBlock *blc = (__bridge struct _LGBlock *)weakBlock;void(^strongBlock)(void) = weakBlock;NSLog(@"weakBlock:%@",weakBlock);NSLog(@"strongBlock:%@",strongBlock);blc->invoke = nil;strongBlock();}-------------------------//输出以下内容:weakBlock:<__NSStackBlock__: 0x16d220f48>strongBlock:<__NSMallocBlock__: 0x2829d2d60>程序闪退,坏地址访问(EXC_BAD_ACCESS)
- 先将栈区
Block赋值给强引用的Block,然后将invoke置空,但依然闪退
闪退原因:Block的赋值只是进行了浅拷贝,相当于对指向对象的指针进行复制,产生一个新的指向对象的指针,但两个指针依然指向同一个对象
解决办法,必须在invoke置空之前,将Block进行深拷贝
- (void)blockDemo{int a = 0;void(^__weak weakBlock)(void) = ^{NSLog(@"a:%d", a);};struct _LGBlock *blc = (__bridge struct _LGBlock *)weakBlock;void(^strongBlock)(void) = [weakBlock copy];NSLog(@"weakBlock:%@",weakBlock);NSLog(@"strongBlock:%@",strongBlock);blc->invoke = nil;NSLog(@"invoke置空");strongBlock();}-------------------------//输出以下内容:weakBlock:<__NSStackBlock__: 0x16d3acf48>strongBlock:<__NSMallocBlock__: 0x28029adf0>invoke置空a:0
使用lldb,观察两个Block在invoke置空前后的变化
- 深拷贝相当于将对象进行复制,产生一个新的对象。并且会递归复制每个指针类型的实例变量,直到两个对象没有任何公共的部分
所以,栈区Block将invoke置空,并不影响堆区Block的调用
2.2 对外部变量的引用计数处理
- (void)blockDemo{NSObject *objc = [NSObject new];NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));void(^strongBlock)(void) = ^{NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));};strongBlock();void(^__weak weakBlock)(void) = ^{NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));};weakBlock();void(^mallocBlock)(void) = [weakBlock copy];mallocBlock();}//输出以下内容:1345
代码解读:
第一步,
objc初始化后的打印1,没有任何问题第二步,在
strongBlock中打印3,拆分成两步进行分析
1、外部objc变量,被栈区Block捕获,引用计数+1
2、栈区Block赋值给强引用的strongBlock,将栈区Block拷贝到堆区,底层进行深拷贝,引用计数也会+1
第三步,赋值给弱引用的
weakBlock,属于栈区Block,仅对外部objc变量进行捕获,引用计数+1第四步,将栈区
Block调用copy方法,赋值给mallocBlock,仅对栈区Block进行了深拷贝,引用计数+1
其实第三步和第四步,等同于第二步的分解,所以打印结果:1、3、4、5
2.3 堆栈Block的释放
- (void)blockDemo{int a = 10;void(^__weak weakBlock)(void) = nil;{void(^strongBlock)(void) = ^{NSLog(@"a:%i", a);};weakBlock = strongBlock;NSLog(@"weakBlock:%@",weakBlock);NSLog(@"strongBlock:%@",strongBlock);}weakBlock();}-------------------------//输出以下内容:weakBlock:<__NSMallocBlock__: 0x280d7cba0>strongBlock:<__NSMallocBlock__: 0x280d7cba0>程序闪退,坏地址访问(EXC_BAD_ACCESS)
代码解读:
第一步,
weakBlock使用__weak修饰,赋值为nil第二步,定义代码块,实现一个堆区
Block第三步,将堆区
Block赋值给代码块外面的weakBlock第四步,在代码块执行完毕后,调用
weakBlock
闪退原因:
在第三步中,堆区
Block赋值__weak修饰的weakBlock,相当于映射关系在第四步中,当代码块执行完毕,
strongBlock由于是堆区Block,出了代码块就会被释放。作为映射的weakBlock,自然也会被置为nil。此时对其进行调用,出现坏地址访问,程序闪退
修改案例:
- (void)blockDemo{int a = 10;void(^__weak weakBlock)(void) = nil;{void(^__weak strongBlock)(void) = ^{NSLog(@"a:%i", a);};weakBlock = strongBlock;NSLog(@"weakBlock:%@",weakBlock);NSLog(@"strongBlock:%@",strongBlock);}weakBlock();}-------------------------//输出以下内容:weakBlock:<__NSStackBlock__: 0x16f6c4f40>strongBlock:<__NSStackBlock__: 0x16f6c4f40>a:10
- 将
strongBlock使用__weak修饰,即可正常打印
当strongBlock使用__weak修饰后,成为栈区Block。将其赋值给__weak修饰的weakBlock,此时依然是栈区Block。栈区Block的生命周期与代码块无关,依赖于函数栈帧,所以可以正常打印
3. Block拷⻉到堆区
如果Block为全局Block,使用任何方式都不会拷贝到堆区,即使手动copy也没用,它依然是全局Block
- (void)blockCopy {void (^block)(void) = ^{NSLog(@"全局Block");};NSLog(@"%@", block);NSLog(@"%@", [block copy]);}-------------------------//输出以下内容:<__NSGlobalBlock__: 0x1009e00f8><__NSGlobalBlock__: 0x1009e00f8>
除此之外,以下四种操作,系统会将Block复制到堆上:
手动
CopyBlock作为返回值被强引用或
Copy修饰系统
API包含usingBlock
3.1 Block作为返回值
由于栈区Block所属的变量域一旦结束,那么该Block就会被销毁。在ARC环境下,编译器会自动的判断,把Block自动的从栈区copy到堆区。例如:当Block作为函数返回值的时候,肯定会copy到堆区
3.2 系统API包含usingBlock
当Block为函数参数时,需要将其手动copy到堆区。但系统API我们不需要处理,比如GCD中携带的usingBlock方法。其他自定义方法传递Block为参数时,都需要进行手动copy
4. 循环引用
对比以下两种Block代码:
self.name = @"lg_cooci";//Block1self.block = ^{NSLog(@"%@",self.name);};//Block2[UIView animateWithDuration:1 animations:^{NSLog(@"%@",self.name);}];
Block1会出现循环引用,因为block被self持有,而block中使用self,所以self又被block持有。这种相互持有的情况下,就会出现循环引用Block2不会出现循环引用,因为block的持有者是UIView,和self无关,不会出现相互持有的情况,所以不会循环引用
对象正常释放的过程:
- 当
A持有B,B的retainCount进行+1 - 当
A触发dealloc时,会给B放信号,B的retainCount进行-1。此时B的retainCount如果为0,就会调用dealloc,正常释放
对象循环引用的过程:
- 当
A和B相互持有时,A的dealloc无法触发,因为A要等B放信号才能对retainCount进行-1 - 但是
B的dealloc也无法触发,因为B也在等待A的信号。此时A和B都在等待对方的释放,最终出现循环引用
避免循环引用的方式:
weak-strong-danceBlock中强行切断持有者将持有者作为
Block参数进行传递和使用
4.1 weak-strong-dance
__weak typeof(self) weakSelf = self;self.block = ^{__strong typeof(self) strongSelf = weakSelf;NSLog(@"%@",strongSelf.name);};
- 将
self赋值给__weak修饰的weakSelf,此时weakSelf属于self的映射,指向同一片内存空间,并且self的引用计数不会发生变化 - 在
Block中将weakSelf赋值给__strong修饰的strongSelf,避免self提前释放导致访问为nil的情况 - 因为
strongSelf为临时变量,在Block作用域结束后,即可自动释放,因此不会循环引用
4.2 Block中强行切断持有者
__block ViewController *vc = self;self.block = ^{NSLog(@"%@",vc.name);vc = nil;};self.block();
- 使用
__block修饰对象,否则vc无法改变,也就是说无法置为nil - 在
Block中使用结束,手动将对象置为nil。相当于手动切断持有关系,可以避免循环引用 - 缺陷:这种方式
Block必须调用,否则将无法手动切断持有关系,self和block都无法释放,最终出现循环引用
4.3 将持有者作为Block参数进行传递和使用
self.vcBlock = ^(ViewController *vc){NSLog(@"%@",vc.name);};self.vcBlock(self);
- 将
self作为参数,提供给Block内部使用。当Block执行结束,vc会自动释放,然后相互持有关系的切断,self也会释放 - 这种方式,
self的是否依赖于Block的执行结束。如果Block中有延迟执行的代码,self的释放也会延迟
4.4 面试题
以下几个案例,是否会出现循环引用?
案例1:
static ViewController *staticSelf_;- (void)blockWeak_static {__weak typeof(self) weakSelf = self;staticSelf_ = weakSelf;}[self blockWeak_static];
- 会出现循环引用
- 将
self赋值__weak修饰的对象,它们属于映射关系,指向同一片内存空间。当weakSelf赋值全局静态变量,staticSelf_在程序运行过程中不会主动释放,它会持续持有self,所以self也无法释放
案例2:
- (void)block_weak_strong {__weak typeof(self) weakSelf = self;self.doWork = ^{__strong typeof(self) strongSelf = weakSelf;weakSelf.doStudent = ^{NSLog(@"%@", strongSelf);};weakSelf.doStudent();};self.doWork();}[self block_weak_strong];
- 会出现循环引用
- 在
doWork内部,strongSelf持有的是self。虽然strongSelf是临时变量,但在doStudent中又被持有,导致引用计数+1。在doWork执行完毕后引用计数-1,但doStudent中的持有还存在,所以会出现循环引用
5. cpp分析
5.1 Block本质
创建block.c文件,写入以下代码:
#include "stdio.h"int main(){void(^block)(void) = ^{printf("LG");};block();return 0;}
- 使用
.c文件,生成的cpp代码更加干净简洁
使用xcrun命令,生成block.cpp文件
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc block.c -o block.cpp
打开block.cpp文件,找到main函数
int main(){void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);return 0;}-------------------------//为了方便阅读,剔除强转代码int main(){void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));block->FuncPtr(block);return 0;}
- 只生成两行代码:
- 调用
__main_block_impl_0函数,传入两个参数,取地址并赋值block - 调用
block的FuncPtr函数,传入block
- 调用
找到__main_block_impl_0的定义:
struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}};//参数1static void __main_block_func_0(struct __main_block_impl_0 *__cself) {printf("LG");}//参数2static struct __main_block_desc_0 {size_t reserved;size_t Block_size;} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
不难看出,
Block的本质是结构体,main函数中调用的是结构体的构造函数结构体中包含两个成员变量
__block_impl结构体类型的impl__main_block_desc_0结构体指针类型的Desc
构造函数中生成了
flags等于0的默认值,赋值impl的Flags参数
fp为Block代码块的函数指针,赋值impl的FuncPtr参数
desc赋值给成员变量Desc所以
main函数中,代码block->FuncPtr(block)就是在对Block进行调用由此可见,当
Block仅定义不调用执行,不会触发Block中的代码块
5.2 捕获外界变量
打开block.c文件,改为以下代码:
#include "stdio.h"int main(){int a = 18;void(^block)(void) = ^{printf("LG - %d",a);};block();return 0;}
生成block.cpp文件,找到main函数
int main(){int a = 18;void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a);block->FuncPtr(block);return 0;}
- 代码发生了改变,之前
__main_block_impl_0函数的入参变成三个 - 增加的第三个参数为外界变量
a
找到__main_block_impl_0的定义:
struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;int a;__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}};//参数1static void __main_block_func_0(struct __main_block_impl_0 *__cself) {int a = __cself->a; // bound by copyprintf("LG - %d",a);}//参数2static struct __main_block_desc_0 {size_t reserved;size_t Block_size;} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
结构体中的成员变量也发生了变化,当捕获外界变量时,在结构体内部,会生成相应的成员变量用来存储
成员变量
a通过结构体的构造函数赋值:a(_a)main函数中调用Bolck,由于捕获外界变量,此时传入的FuncPtr中的block发挥作用block为自身结构体的指针,将block中的成员变量a赋值给临时变量a,然后对其打印临时变量
a和__cself->a的值相同,但地址不同由于
a是值拷贝,Bolck的代码块中不能对a的值进行改变,会造成编译器的代码歧义。所以,此时的a是只读的
捕获外界变量并赋值强引用变量,本该是堆区
Block,但结构体中impl的isa赋值为&_NSConcreteStackBlock,标记为栈区Block。因为在编译时,无法开辟内存空间,所以暂且标记为StackBlock。在运行时,会根据情况将Block拷贝到堆区,然后生成MallocBlock
5.3 __block的作用
打开block.c文件,改为以下代码:
#include "stdio.h"int main(){__block int a = 18;void(^block)(void) = ^{a++;printf("LG - %d",a);};block();return 0;}
生成block.cpp文件,找到main函数
int main(){__Block_byref_a_0 a = {0,(__Block_byref_a_0 *)&a,0,sizeof(__Block_byref_a_0),18};void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344);block->FuncPtr(block);return 0;}
int类型a,对应生成__Block_byref_a_0结构体。成员变量2,对结构体a取地址,转为结构体指针
找到__Block_byref_a_0定义:
struct __Block_byref_a_0 {void *__isa;__Block_byref_a_0 *__forwarding;int __flags;int __size;int a;};
- 其中
__forwarding存储的就是a结构体的地址 - 最后的成员变量
a存储18的值 - 结构体中存储了自身的地址和值
找到__main_block_impl_0的定义:
struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;__Block_byref_a_0 *a; // by ref__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}};//参数1static void __main_block_func_0(struct __main_block_impl_0 *__cself) {__Block_byref_a_0 *a = __cself->a; // bound by ref(a->__forwarding->a)++;printf("LG - %d",(a->__forwarding->a));}//参数2static struct __main_block_desc_0 {size_t reserved;size_t Block_size;void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);void (*dispose)(struct __main_block_impl_0*);} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};//参数2使用的copy和dispose函数static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
局部变量
a和__cself->a指针地址相同,它的值一旦改变,相当于对外界变量的值进行修改没有
__block修饰,属于值拷贝,也就是深拷贝。拷贝的值不可更改,它们指向不同的内存空间使用
__block修饰,属于地址拷贝,也就是浅拷贝。生成的对象指向同一片内存空间,内部修改等同于对外界变量的修改
6. 汇编分析
6.1 流程分析
搭建App项目,写入以下代码:
- (void)viewDidLoad {[super viewDidLoad];__block NSObject *objc = [NSObject alloc];void (^block)(void) = ^{NSLog(@"LG_Block %@ ",objc);};block();}
针对block的定义设置断点,运行项目,查看汇编代码
在项目中,设置objc_retainBlock符号断点
- 来自
libobjc.A.dylib框架
打开objc4-818.2源码,找到objc_retainBlock函数
id objc_retainBlock(id x) {return (id)_Block_copy(x);}
- 调用
_Block_copy函数,但是objc源码中找不到它的实现
在项目中,设置_Block_copy符号断点
来自libsystem_blocks.dylib框架,但该框架暂未开源,可以在libclosure替代工程中查看
6.2 Block结构
通过cpp分析,Block的本质是结构体。在libclosure-79源码中,来到_Block_copy函数,可以找到Block的真实类型:Block_layout
找到Block_layout的定义:
struct Block_layout {void * __ptrauth_objc_isa_pointer isa;volatile int32_t flags; // contains ref countint32_t reserved;BlockInvokeFunction invoke;struct Block_descriptor_1 *descriptor;// imported variables};
isa:标示Block类型的类flags:标识符,按bit位表示Block的附加信息,类似于isa中的位域reserved:预留位置invoke:函数指针,指向Block实现的调用地址descriptor:附加信息,例如:存储保留变量数、Block的大小、进行copy或dispose的函数指针
找到flags的标示:
enum {BLOCK_DEALLOCATING = (0x0001), // runtimeBLOCK_REFCOUNT_MASK = (0xfffe), // runtimeBLOCK_INLINE_LAYOUT_STRING = (1 << 21), // compiler#if BLOCK_SMALL_DESCRIPTOR_SUPPORTEDBLOCK_SMALL_DESCRIPTOR = (1 << 22), // compiler#endifBLOCK_IS_NOESCAPE = (1 << 23), // compilerBLOCK_NEEDS_FREE = (1 << 24), // runtimeBLOCK_HAS_COPY_DISPOSE = (1 << 25), // compilerBLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ codeBLOCK_IS_GC = (1 << 27), // runtimeBLOCK_IS_GLOBAL = (1 << 28), // compilerBLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATUREBLOCK_HAS_SIGNATURE = (1 << 30), // compilerBLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler};
BLOCK_DEALLOCATING:释放标记,一般常用于BLOCK_BYREF_NEEDS_FREE做位与运算,一同传入flags,告知该Block可释放BLOCK_REFCOUNT_MASK:存储引用引用计数的值,是一个可选用参数BLOCK_NEEDS_FREE:低16位是否有效的标志,程序根据它来决定是否增加或者减少引用计数位的值BLOCK_HAS_COPY_DISPOSE:是否拥有拷贝辅助函数,用于拷贝到堆区,决定block_description_2BLOCK_HAS_CTOR:是否拥有Block的C++析构函数BLOCK_IS_GC:标志是否有垃圾回收,OSXBLOCK_IS_GLOBAL:标志是否是全局BlockBLOCK_USE_STRET:与BLOCK_HAS_SIGNATURE相对,判断是否当前Block拥有一个签名,用于runtime时动态调用BLOCK_HAS_SIGNATURE:是否有签名BLOCK_HAS_EXTENDED_LAYOUT:是否有拓展,决定block_description_3
6.3 运行时Copy
进入_Block_copy函数的汇编代码,读取x0寄存器的值,并对其进行打印
- 进入
_Block_copy函数,当前Block标记为StackBlock
直接运行到函数结尾,读取返回值x0寄存器,并对其进行打印
- 程序运行时, 当前
Block符合MallocBlock的条件,经过_Block_copy函数,会将Block复制到堆区
6.4 Block调用
_Block_copy函数执行完毕,回到viewDidLoad方法,继续进行Block的调用
ldr x8, [x0, #0x10]:x0为当前Block,读取x0 + 16字节的值,赋值给x8Block_layout结构体中,首地址偏移16字节,相当于跳过isa、flags和reserved,读取到invoke函数地址,赋值给x8
blr x8:跳转到invoke函数地址,相当于Block的调用
6.5 descriptor
当_Block_copy函数执行完毕,打印当前Block为MallocBlock
- 同时还打印出
signature、copy、dispose等数据
6.5.1 descriptor类型
signature、copy、dispose等数据,存储在Block_layout结构体的descriptor中
descriptor分为三种类型:
Block_descriptor_1Block_descriptor_2Block_descriptor_3
其中Block_descriptor_1一定存在,Block_descriptor_2和Block_descriptor_3为可选
找到descriptor的定义:
#define BLOCK_DESCRIPTOR_1 1struct Block_descriptor_1 {uintptr_t reserved;uintptr_t size;};#define BLOCK_DESCRIPTOR_2 1struct Block_descriptor_2 {// requires BLOCK_HAS_COPY_DISPOSEBlockCopyFunction copy;BlockDisposeFunction dispose;};#define BLOCK_DESCRIPTOR_3 1struct Block_descriptor_3 {// requires BLOCK_HAS_SIGNATUREconst char *signature;const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT};
Block_descriptor_1:存储预留字段和Block大小Block_descriptor_2:存储copy和dispose的函数指针Block_descriptor_3:存储signature签名和layout
Block_descriptor_1的读取:
static struct Block_descriptor_1 * _Block_descriptor_1(struct Block_layout *aBlock){return aBlock->descriptor;}
- 由于
Block_descriptor_1一定存在,直接通过Block的descriptor读取
Block_descriptor_2的读取:
static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock){if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;uint8_t *desc = (uint8_t *)aBlock->descriptor;desc += sizeof(struct Block_descriptor_1);return (struct Block_descriptor_2 *)desc;}
- 通过位与运算,判断
Block_descriptor_2不存在,返回NULL - 否则,读取
Block_descriptor_1的首地址,偏移自身大小,即:Block_descriptor_2的首地址 - 强转为
Block_descriptor_2的结构体指针返回
Block_descriptor_3的读取:
static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock){if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return NULL;uint8_t *desc = (uint8_t *)aBlock->descriptor;desc += sizeof(struct Block_descriptor_1);if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {desc += sizeof(struct Block_descriptor_2);}return (struct Block_descriptor_3 *)desc;}
- 通过位与运算,判断
Block_descriptor_3不存在,返回NULL - 否则,读取
Block_descriptor_1的首地址,偏移自身大小,即:Block_descriptor_2的首地址 - 通过位与运算,判断
Block_descriptor_2是否存在- 存在,再偏移
Block_descriptor_2大小,即:Block_descriptor_3的首地址 - 不存在,强转为
Block_descriptor_3的结构体指针返回
- 存在,再偏移
因为Block_descriptor_2和Block_descriptor_3的内存结构相同,所以偏移Block_descriptor_1之后的地址,即可强转为2,可强转为3
6.5.2 lldb验证descriptor
通过flags进行位与运算,可以得知Block_descriptor_2和Block_descriptor_3是否存在
使用x/8g命令,输出Block的内存结构
(lldb) x/8g 0x283aaa4300x283aaa430: 0x00000001de1c0880 0x00000000c30000020x283aaa440: 0x00000001021422ac 0x00000001021440180x283aaa450: 0x0000000283aaa400 0x00000000000000000x283aaa460: 0x000021a1de1c6221 0x0000000000000000
0xc3000002:Block的flags标识符
验证Block_descriptor_2:
//BLOCK_HAS_COPY_DISPOSE = (1 << 25)(lldb) p/x 1 << 25(int) $7 = 0x02000000(lldb) p/x (0xc3000002 & 0x02000000)(unsigned int) $8 = 0x02000000
- 运算结果不为
0,证明Block_descriptor_2存在
验证Block_descriptor_3:
//BLOCK_HAS_SIGNATURE = (1 << 30)(lldb) p/x 1 << 30(int) $10 = 0x40000000(lldb) p/x (0xc3000002 & 0x40000000)(unsigned int) $16 = 0x40000000
- 运算结果不为
0,证明Block_descriptor_3存在
6.6 Block签名
6.6.1 lldb验证签名
使用x/8g命令,输出Block的内存结构
(lldb) x/8g 0x283aaa4300x283aaa430: 0x00000001de1c0880 0x00000000c30000020x283aaa440: 0x00000001021422ac 0x00000001021440180x283aaa450: 0x0000000283aaa400 0x00000000000000000x283aaa460: 0x000021a1de1c6221 0x0000000000000000
- 首地址平移
24字节,0x0000000102144018就是descriptor的结构体指针
使用x/8g命令,输出descriptor的内存结构
(lldb) x/8g 0x00000001021440180x102144018: 0x0000000000000000 0x00000000000000280x102144028: 0x00000001021422e0 0x00000001021422f00x102144038: 0x00000001021433e3 0x00000000000000100x102144048: 0x00000001de78a280 0x00000000000007c8
0x00000001021422e0:copy函数指针0x00000001021422f0:dispose函数指针0x00000001021433e3:signature签名
对0x00000001021433e3进行强转输出
(lldb) po (char *)0x00000001021433e3"v8@?0"
6.6.2 签名含义
对于签名v8@?0的解释:
v:返回值类型viod8:方法所占用的内存8字节@?:参数0类型0:参数0的起始位置,从0字节开始
签名的详细信息,可以使用NSMethodSignature的signatureWithObjCTypes方法输出
(lldb) po [NSMethodSignature signatureWithObjCTypes:"v8@?0"]<NSMethodSignature: 0xbb2001894a750adf>number of arguments = 1frame size = 224is special struct return? NOreturn value: -------- -------- -------- --------type encoding (v) 'v'flags {}modifiers {}frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}memory {offset = 0, size = 0}argument 0: -------- -------- -------- --------type encoding (@) '@?'flags {isObject, isBlock}modifiers {}frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}memory {offset = 0, size = 8}
number of arguments = 1:表示传入一个参数is special struct return? NO:没有返回值return value:返回值type encoding (v) 'v':void类型的缩写memory {offset = 0, size = 0}:无返回值,故此size为0
argument 0:参数0type encoding (@) '@?':其中@为id类型,?未知类型flags {isObject, isBlock}:即是Object类型,也是Block类型memory {offset = 0, size = 8}:参数0从0字节开始,占8字节
7. 源码分析
使用替代工程libclosure进行源码分析:
当Block捕获使用__block修饰的对象,底层会触发Block的三层拷贝
7.1 _Block_copy
进入_Block_copy函数
void *_Block_copy(const void *arg) {struct Block_layout *aBlock;// 如果 arg 为 NULL,直接返回 NULLif (!arg) return NULL;// The following would be better done as a switch statement// 强转为 Block_layout 类型aBlock = (struct Block_layout *)arg;// 如果现在已经在堆上if (aBlock->flags & BLOCK_NEEDS_FREE) {// latches on high// 就只将引用计数加 1latching_incr_int(&aBlock->flags);return aBlock;}// 如果 block 在全局区,不用加引用计数,也不用拷贝,直接返回 block 本身else if (aBlock->flags & BLOCK_IS_GLOBAL) {return aBlock;}else {// 栈 - 堆 (编译期)// Its a stack block. Make a copy.// block 现在在栈上,现在需要将其拷贝到堆上// 在堆上重新开辟一块和 aBlock 相同大小的内存size_t size = Block_size(aBlock);struct Block_layout *result = (struct Block_layout *)malloc(size);// 开辟失败,返回 NULLif (!result) return NULL;// 将 aBlock 内存上的数据全部复制新开辟的 result 上memmove(result, aBlock, size); // bitcopy first#if __has_feature(ptrauth_calls)// Resign the invoke pointer as it uses address authentication.result->invoke = aBlock->invoke;#if __has_feature(ptrauth_signed_block_descriptors)if (aBlock->flags & BLOCK_SMALL_DESCRIPTOR) {uintptr_t oldDesc = ptrauth_blend_discriminator(&aBlock->descriptor,_Block_descriptor_ptrauth_discriminator);uintptr_t newDesc = ptrauth_blend_discriminator(&result->descriptor,_Block_descriptor_ptrauth_discriminator);result->descriptor =ptrauth_auth_and_resign(aBlock->descriptor,ptrauth_key_asda, oldDesc,ptrauth_key_asda, newDesc);}#endif#endif// reset refcount// 将 flags 中的 BLOCK_REFCOUNT_MASK 和 BLOCK_DEALLOCATING 部分的位全部清为 0result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed// 将 result 标记位在堆上,需要手动释放;并且引用计数初始化为 1result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1// copy 方法中会调用做拷贝成员变量的工作_Block_call_copy_helper(result, aBlock);// Set isa last so memory analysis tools see a fully-initialized object.// isa 指向 _NSConcreteMallocBlockresult->isa = _NSConcreteMallocBlock;return result;}}
_Block_copy函数,负责Block对象的自身拷贝,从栈区拷贝到堆区参数
arg就是Block_layout对象如果原来就在堆上,就将引用计数
+1如果
Block在全局区,不用加引用计数,也不用拷贝,直接返回Block本身如果原来在栈上,会拷贝到堆上,引用计数初始化为
1,并且会调用_Block_call_copy_helper方法(如果存在的话)返回值是拷贝后
Block的地址
7.2 _Block_object_assign
7.2.1 cpp分析
打开cpp文件,找到声明Block时的第二个参数,__main_block_desc_0_DATA结构体的定义:
//void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344);__main_block_desc_0_DATA = {0,sizeof(struct __main_block_impl_0),__main_block_copy_0,__main_block_dispose_0};
__main_block_copy_0:Block_descriptor_2中的copy__main_block_dispose_0:Block_descriptor_2中的dispose
找到__main_block_copy_0的定义:
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
- 调用
_Block_object_assign函数
7.2.2 lldb分析
使用x/8g命令,输出Block的内存结构
(lldb) x/8g 0x000000028167eca00x28167eca0: 0x00000001de1c0880 0x00000000c30000020x28167ecb0: 0x0000000104f1a2ac 0x0000000104f1c0180x28167ecc0: 0x000000028167ec70 0x00000000000000000x28167ecd0: 0x000021a1de1c6221 0x0000000000000000
使用x/8g命令,输出descriptor的内存结构
(lldb) x/8g 0x0000000104f1c0180x104f1c018: 0x0000000000000000 0x00000000000000280x104f1c028: 0x0000000104f1a2e0 0x0000000104f1a2f00x104f1c038: 0x0000000104f1b3e3 0x00000000000000100x104f1c048: 0x00000001de78a280 0x00000000000007c8
0x0000000104f1a2e0:copy函数指针
使用dis -s读取汇编代码
(lldb) dis -s 0x0000000104f1a2e0004-Block结构与签名`__copy_helper_block_e8_32r:0x104f1a2e0 <+0>: add x0, x0, #0x20 ; =0x200x104f1a2e4 <+4>: ldr x1, [x1, #0x20]0x104f1a2e8 <+8>: mov w2, #0x80x104f1a2ec <+12>: b 0x104f1a484 ; symbol stub for: _Block_object_assign
- 调用
_Block_object_assign函数
7.2.3 调用时机
在_Block_copy函数中,调用_Block_call_copy_helper函数
static void _Block_call_copy_helper(void *result, struct Block_layout *aBlock){if (auto *pFn = _Block_get_copy_function(aBlock))pFn(result, aBlock);}
进入_Block_get_copy_function函数
static inline __typeof__(void (*)(void *, const void *))_Block_get_copy_function(struct Block_layout *aBlock){if (!(aBlock->flags & BLOCK_HAS_COPY_DISPOSE))return NULL;void *desc = _Block_get_descriptor(aBlock);#if BLOCK_SMALL_DESCRIPTOR_SUPPORTEDif (aBlock->flags & BLOCK_SMALL_DESCRIPTOR) {struct Block_descriptor_small *bds =(struct Block_descriptor_small *)desc;return _Block_get_relative_function_pointer(bds->copy, void (*)(void *, const void *));}#endifstruct Block_descriptor_2 *bd2 =(struct Block_descriptor_2 *)((unsigned char *)desc +sizeof(struct Block_descriptor_1));return _Block_get_copy_fn(bd2);}
- 如果存在
copy和dispose,通过内存获取Block_descriptor_2的结构体指针 - 调用
_Block_get_copy_fn函数,传入bd2
进入_Block_get_copy_fn函数
static inline __typeof__(void (*)(void *, const void *))_Block_get_copy_fn(struct Block_descriptor_2 *desc){return (void (*)(void *, const void *))_Block_get_function_pointer(desc->copy);}
- 经过处理返回
Block_descriptor_2下的copy函数
最终,回到_Block_call_copy_helper函数,将copy函数地址赋值pFn,然后通过pFn(result, aBlock)对其进行调用
7.2.4 源码分析
在源码中,找到Block捕获外界变量的种类
enum {// see function implementation for a more complete description of these fields and combinationsBLOCK_FIELD_IS_OBJECT = 3, // id, NSObject, __attribute__((NSObject)), block, ...BLOCK_FIELD_IS_BLOCK = 7, // a block variableBLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variableBLOCK_FIELD_IS_WEAK = 16, // declared __weak, only used in byref copy helpersBLOCK_BYREF_CALLER = 128, // called from __block (byref) copy/dispose support routines.};
BLOCK_FIELD_IS_OBJECT:普通对象类型BLOCK_FIELD_IS_BLOCK:Block类型作为变量BLOCK_FIELD_IS_BYREF:使用__block修饰的变量BLOCK_FIELD_IS_WEAK:weak弱引用变量BLOCK_BYREF_CALLER:返回的调用对象,处理block_byref内部对象内存会加的一个额外标记,配合flags一起使用
进入_Block_object_assign函数
// 当 block 和 byref 要持有对象时,它们的 copy helper 函数会调用这个函数来完成 assignment// 参数 destAddr 其实是一个二级指针,指向真正的目标指针void _Block_object_assign(void *destArg, const void *object, const int flags) {const void **dest = (const void **)destArg;switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {case BLOCK_FIELD_IS_OBJECT:/*******id object = ...;[^{ object; } copy];********/// _Block_retain_object_default = fn (arc)// 默认什么都不干,但在 _Block_use_RR() 中会被 Objc runtime 或者 CoreFoundation 设置 retain 函数// 其中,可能会与 runtime 建立联系,操作对象的引用计数什么的// 可以理解为交给系统 ARC 处理_Block_retain_object(object);// 使 dest 指向的目标指针指向 object*dest = object;break;case BLOCK_FIELD_IS_BLOCK:/*******void (^object)(void) = ...;[^{ object; } copy];********/// 使 dest 指向拷贝到堆上object*dest = _Block_copy(object);break;case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:case BLOCK_FIELD_IS_BYREF:/*******// copy the onstack __block container to the heap// Note this __weak is old GC-weak/MRC-unretained.// ARC-style __weak is handled by the copy helper directly.__block ... x;__weak __block ... x;[^{ x; } copy];********/// 使 dest 指向拷贝到堆上的byref*dest = _Block_byref_copy(object);break;case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:/*******// copy the actual field held in the __block container// Note this is MRC unretained __block only.// ARC retained __block is handled by the copy helper directly.__block id object;__block void (^object)(void);[^{ object; } copy];********/// 使 dest 指向的目标指针指向 object*dest = object;break;case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:/*******// copy the actual field held in the __block container// Note this __weak is old GC-weak/MRC-unretained.// ARC-style __weak is handled by the copy helper directly.__weak __block id object;__weak __block void (^object)(void);[^{ object; } copy];********/// 使 dest 指向的目标指针指向 object*dest = object;break;default:break;}}
普通对象类型,交给系统
ARC处理,使dest指向的目标指针指向objectBlock类型作为变量,调用_Block_copy函数,使dest指向拷贝到堆上object使用
__block修饰的变量,调用_Block_byref_copy函数,使dest指向拷贝到堆上的byref
7.3 _Block_byref_copy
进入_Block_byref_copy函数
// 1. 如果 byref 原来在堆上,就将其拷贝到堆上,拷贝的包括 Block_byref、Block_byref_2、Block_byref_3// 被 __weak 修饰的 byref 会被修改 isa 为 _NSConcreteWeakBlockVariable// 原来 byref 的 forwarding 也会指向堆上的 byref;// 2. 如果 byref 已经在堆上,就只增加一个引用计数。static struct Block_byref *_Block_byref_copy(const void *arg) {// arg 强转为 Block_byref * 类型struct Block_byref *src = (struct Block_byref *)arg;// 引用计数等于 0if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {// src points to stack// 为新的 byref 在堆中分配内存struct Block_byref *copy = (struct Block_byref *)malloc(src->size);copy->isa = NULL;// byref value 4 is logical refcount of 2: one for caller, one for stack// 新 byref 的 flags 中标记了它是在堆上,且引用计数为 2。// 为什么是 2 呢?注释说的是 non-GC one for caller, one for stack// one for caller 很好理解,那 one for stack 是为什么呢?// 看下面的代码中有一行 src->forwarding = copy。src 的 forwarding 也指向了 copy,相当于引用了 copycopy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;// 堆上 byref 的 forwarding 指向自己copy->forwarding = copy; // patch heap copy to point to itself// 原来栈上的 byref 的 forwarding 现在也指向堆上的 byrefsrc->forwarding = copy; // patch stack to point to heap copy// 拷贝 sizecopy->size = src->size;// 如果 src 有 copy/dispose helperif (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {// Trust copy helper to copy everything of interest// If more than one field shows up in a byref block this is wrong XXX// 取得 src 和 copy 的 Block_byref_2struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);// copy 的 copy/dispose helper 也与 src 保持一致// 因为是函数指针,估计也不是在栈上,所以不用担心被销毁copy2->byref_keep = src2->byref_keep;copy2->byref_destroy = src2->byref_destroy;// 如果 src 有扩展布局,也拷贝扩展布局if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);// 没有将 layout 字符串拷贝到堆上,是因为它是 const 常量,不在栈上copy3->layout = src3->layout;}// 调用 copy helper,因为 src 和 copy 的 copy helper 是一样的,所以用谁的都行,调用的都是同一个函数// 发起第三层拷贝(*src2->byref_keep)(copy, src);}else {// Bitwise copy.// This copy includes Block_byref_3, if any.// 如果 src 没有 copy/dispose helper// 将 Block_byref 后面的数据都拷贝到 copy 中,一定包括 Block_byref_3memmove(copy+1, src+1, src->size - sizeof(*src));}}// already copied to heap// src 已经在堆上,就只将引用计数加 1else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {latching_incr_int(&src->forwarding->flags);}return src->forwarding;}
对
Block_byref进行拷贝,属于三层拷贝中的第二层如果引用计数为
0,为新的byref在堆中分配内存将堆上
byref的forwarding指向自己将原来栈上的
byref的forwarding也指向堆上的byref由
byref_keep发起Block的第三层拷贝
如果已经在堆上,就只将引用计数
+1
7.3.1 byref_keep分析
找到byref_keep的定义:
struct Block_byref {void *isa;struct Block_byref *forwarding;volatile int32_t flags; // contains ref countuint32_t size;};struct Block_byref_2 {// requires BLOCK_BYREF_HAS_COPY_DISPOSEBlockByrefKeepFunction byref_keep;BlockByrefDestroyFunction byref_destroy;};struct Block_byref_3 {// requires BLOCK_BYREF_LAYOUT_EXTENDEDconst char *layout;};
对应cpp文件查看:
struct __Block_byref_objc_0 {void *__isa;__Block_byref_objc_0 *__forwarding;int __flags;int __size;void (*__Block_byref_id_object_copy)(void*, void*);void (*__Block_byref_id_object_dispose)(void*);NSObject *objc;};
- 源码中的
byref_keep,对应cpp中的__Block_byref_id_object_copy
找到__Block_byref_id_object_copy的赋值
__Block_byref_objc_0 objc = {(void*)0,(__Block_byref_objc_0 *)&objc,33554432,sizeof(__Block_byref_objc_0),__Block_byref_id_object_copy_131,__Block_byref_id_object_dispose_131,((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc"))};
- 传入
__Block_byref_id_object_copy_131
找到__Block_byref_id_object_copy_131的定义:
static void __Block_byref_id_object_copy_131(void *dst, void *src) {_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);}
对应源码查看传入的参数:
(*src2->byref_keep)(copy, src);
- 传入堆上的
Block_byref,对应cpp中的__Block_byref_objc_0结构体
7.3.2 byref_keep的结论
调用byref_keep,底层又调用一次_Block_object_assign函数
(char*)dst + 40参数,结构体内存平移,传入的就是objc实例对象
进入_Block_object_assign函数,命中普通对象类型的copy逻辑
将对象拷贝到堆区,完成Block的第三层拷贝
7.4 Block三层拷贝的结论
当Block捕获使用__block修饰的对象,底层会触发Block的三层拷贝
【第一层】
_Block_copy函数,负责Block对象的自身拷贝,从栈区拷贝到堆区通过
_Block_copy→_Block_call_copy_helper,调用_Block_object_assign函数传入
Block_byref结构体指针,类型为BLOCK_FIELD_IS_BYREF,调用_Block_byref_copy函数
【第二层】
_Block_byref_copy函数,将Block_byref拷贝到堆区- 通过
byref_keep函数,调用_Block_object_assign函数,传入objc实例对象
- 通过
【第三层】
_Block_object_assign函数,将捕获的外界变量拷贝到堆区
7.5 Block释放
如果Block在堆上,需要进行release。在全局区和栈区的Block,都不需要release
7.5.1 _Block_release
进入_Block_release函数
// block 在堆上,才需要 release,在全局区和栈区都不需要 release.// 先将引用计数减 1,如果引用计数减到了 0,就将 block 销毁void _Block_release(const void *arg) {struct Block_layout *aBlock = (struct Block_layout *)arg;// 如果 block == nilif (!aBlock) return;// 如果 block 在全局区if (aBlock->flags & BLOCK_IS_GLOBAL) return;// block 不在堆上if (! (aBlock->flags & BLOCK_NEEDS_FREE)) return;// 引用计数减 1,如果引用计数减到了 0,会返回 true,表示 block 需要被销毁if (latching_decr_int_should_deallocate(&aBlock->flags)) {// 调用 block 的 dispose helper,dispose helper 方法中会做诸如销毁 byref 等操作_Block_call_dispose_helper(aBlock);// _Block_destructInstance 啥也不干,函数体是空的_Block_destructInstance(aBlock);free(aBlock);}}
- 和
_Block_copy相似,通过_Block_call_dispose_helper函数,调用_Block_object_dispose函数
7.5.2 _Block_object_dispose
进入_Block_object_dispose函数
// 当 block 和 byref 要 dispose 对象时,它们的 dispose helper 会调用这个函数void _Block_object_dispose(const void *object, const int flags) {switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {// 如果是 byrefcase BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:case BLOCK_FIELD_IS_BYREF:// get rid of the __block data structure held in a Block// 对 byref 对象做 release 操作_Block_byref_release(object);break;case BLOCK_FIELD_IS_BLOCK:// 对 block 做 release 操作_Block_release(object);break;case BLOCK_FIELD_IS_OBJECT:// 默认啥也不干,但在 _Block_use_RR() 中可能会被 Objc runtime 或者 CoreFoundation 设置一个 release 函数,里面可能会涉及到 runtime 的引用计数_Block_release_object(object);break;case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:break;default:break;}}
普通对象类型,交给系统
ARC处理Block类型作为变量,调用_Block_release函数使用
__block修饰的变量,调用_Block_byref_release函数,对byref对象做release操作
7.5.3 _Block_byref_release
进入_Block_byref_release函数
// 对 byref 对象做 release 操作,// 堆上的 byref 需要 release,栈上的不需要 release,// release 就是引用计数减 1,如果引用计数减到了 0,就将 byref 对象销毁static void _Block_byref_release(const void *arg) {struct Block_byref *byref = (struct Block_byref *)arg;// dereference the forwarding pointer since the compiler isn't doing this anymore (ever?)// 取得真正指向的 byref,如果 byref 已经被堆拷贝,则取得是堆上的 byref,否则是栈上的,栈上的不需要 release,也没有引用计数byref = byref->forwarding;// byref 被拷贝到堆上,需要 releaseif (byref->flags & BLOCK_BYREF_NEEDS_FREE) {// 取得引用计数int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK;os_assert(refcount);// 引用计数减 1,如果引用计数减到了 0,会返回 true,表示 byref 需要被销毁if (latching_decr_int_should_deallocate(&byref->flags)) {if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);// dispose helper 藏在 Block_byref_2 里(*byref2->byref_destroy)(byref);}free(byref);}}}
- 和
_Block_byref_copy相似,由byref_destroy发起对象的release
对应cpp代码:
static void __Block_byref_id_object_dispose_131(void *src) {_Block_object_dispose(*(void * *) ((char*)src + 40), 131);}
调用
_Block_object_dispose函数,传入objc实例对象进入
_Block_object_dispose函数,命中普通对象类型的release逻辑
8. 总结
Block类型:
GlobalBlock位于全局区
在
Block内部不能捕获外部变量,或只使用静态变量或全局变量
MallocBlock位于堆区
在
Block内部使用局部变量或OC属性,并且赋值给强引用或Copy修饰的变量
StackBlock位于栈区
与
MallocBlock一样,可以在内部使用局部变量或OC属性。但不能赋值给强引用或Copy修饰的变量
以下四种操作,系统会将Block复制到堆上:
手动
CopyBlock作为返回值被强引用或
Copy修饰系统
API包含usingBlock
避免Block循环引用的方式:
weak-strong-danceBlock中强行切断持有者将持有者作为
Block参数进行传递和使用
当Block捕获使用__block修饰的对象,底层会触发Block的三层拷贝:
【第一层】
_Block_copy函数,负责Block对象的自身拷贝,从栈区拷贝到堆区通过
_Block_copy→_Block_call_copy_helper,调用_Block_object_assign函数传入
Block_byref结构体指针,类型为BLOCK_FIELD_IS_BYREF,调用_Block_byref_copy函数
【第二层】
_Block_byref_copy函数,将Block_byref拷贝到堆区- 通过
byref_keep函数,调用_Block_object_assign函数,传入objc实例对象
- 通过
【第三层】
_Block_object_assign函数,将捕获的外界变量拷贝到堆区
Block释放:
如果
Block在堆上,需要进行release。在全局区和栈区的Block,都不需要release释放流程和
Block拷贝流程基本一致
