前言
在开发中Block的使用是必不可少的,它可以作为参数或者函数的返回值。但使用Block过程中,还是多少会有些问题,下面我们对它进行一个全面的分析
常见的Block
-
常见的
Block有三种:-
GlobalBlock:
- 位于全局区,在
Block内部不使用外部局部变量,或者只使用静态变量或者全局变量
-
MallocBlock:
- 位于堆区,在
Block内部使用局部变量或者OC属性,并且赋值给强引用或者Copy修饰的变量
-
StackBlock:
- 栈
block位于栈区,与MallocBlock一样可以在内部使用局部变量或者OC属性,但是不能赋值给强引用或者Copy修饰的变量。
-
-
3种
Block在ARC中的代码体现:
可能存在的误区
- 手动
Copy后Block就变成了MallocBlock吗?- 不一定,如果没有使用
局部变量或者OC属性,即使copy还是原来的类型
- 不一定,如果没有使用
Block对引用计数的影响
- 先来看一个题目
- (void)blockRetainCountTest { NSObject *objc = [NSObject new]; // 1 NSLog(@"print1 retainCount %ld", CFGetRetainCount((__bridge CFTypeRef)(objc))); void (^strongBlock)(void) = ^{ // MallocBlock NSLog(@"print2 retainCount %ld", CFGetRetainCount((__bridge CFTypeRef)(objc))); }; strongBlock(); void (^__weak weakBlock)(void) = ^{ // StackBlock NSLog(@"print3 retainCount %ld", CFGetRetainCount((__bridge CFTypeRef)(objc))); }; weakBlock(); void (^ mallocBlock)(void) = [weakBlock copy]; // MallocBlock mallocBlock(); }-
这个几个打印分别打印几呢?
print1毋庸置疑肯定是1,那么第二个呢?先来看下输出结果: -
打印结果为
1,3,4,5,这里可能就会有疑问了,第二个怎么是3?分析如下:- 第二个是
MallocBlock,它对objc进行了持有,所以引用计数+1,但是在底层堆Block会拷贝一份,所以引用技术再+1就变成了3 - 第三个是
StactBlock,它只对objc进行了持有,所以引用计数+1之后变成4 - 第四个
Block是对栈Block进行了copy变成了MallocBlock,引用计数再+1变成5 - 第三和第四种结合起来就是第二种
Block的情况
- 第二个是
-
Block堆栈释放差异
-
一、来看看
Block再对栈中的案例:- (void)blockTest { int a = 20; void(^__weak weakBlock)(void) = nil; { void(^__weak strongBlock)(void) = ^{ NSLog(@"---%d", a); }; weakBlock = strongBlock; NSLog(@"1 - %@ - %@",weakBlock,strongBlock); } weakBlock(); }- 这个打印是怎样呢?
-
结果正常执行。这里可能会产生的误区:
-
{}是作用域,不是block块
-
weakBlock是StackBlock,它的作用域与{}无关,与blockTest函数有关,所以出了{}后它的内存还在不会释放,所以可以正常调用
-
-
如果
a换成对象类型,又是怎样呢?- 结果
a无法打印,为什么? - 因为在
strongBlock中捕获了a指针,捕获对象会copy一份到堆,所以出作用域{}后,捕获的这个a内存会被释放,所以会打印null
- 结果
-
二、将题目修改下把
strongBlock的__weak去掉,然后再运行:-
此时产生崩溃
why?- 由于此时的
strongBlock是MallocBlock,它在出{}后释放了,所以外面weakBlock调用时,实质是空地址调用,于是就产生崩溃
- 由于此时的
-
-
三、两个
block都是MallocBlock:- 这又可以输出,不是出作用域释放了么?
- 此时
weakBlock和strongBlock都是MallocBlock,赋值后weakBlock对strongBlock进行了 强持有,所以出{}作用域后strongBlock并没有释放,所以可以调用成功
- 此时
- 这又可以输出,不是出作用域释放了么?
循环引用
产生循环引用的原因
-
Block常见的问题就是循环引用,什么是循环引用呢?,首先来看下对象的持有与释放流程:- 当对象
B被持有时引用计数会+1,当A需要释放时,A会发送release信号给B,如果retainCount为0,则B发送dealloc信号给A,然后A释放 - 循环引用的过程也比较好理解,就是
A持有B,但B中也有A;A需要释放时就会发送release给B,等待B发送dealloc消息,但B因持有A所以需要A释放后才会发送,所以就形成了相互等待,如下图:
- 当对象
案例分析
-
经典案例:
@property (nonatomic, copy) dispatch_block_t block; @property (nonatomic, copy) NSString *name; - (void)retainCycleDemo1 { self.name = @"wushuang"; self.block = ^{ NSLog(@"name is %@", self.name); }; self.block(); } - (void)dealloc { NSLog(@" 释放啦~~~ 🎉🎉🎉 "); }- 代码中
self -> block -> self,导致循环引用,进而导致VC无法释放
那么这个问题怎么解决呢?我们都知道用
weakSelf来解决,但问题真能解决吗?哦豁,确实可以释放了,但任务还没执行就销毁了,不是我们想要的效果啊,此时就需要用
weak-strong搭配使用了,也就是强弱共舞 - 代码中
解决方案
-
- 强弱共舞
strongSelf是一个局部变量,也就是self在块任务这个区间被强引用,当任务执行完也就不存在了。当需要dealloc时,如果block没有执行完,但由于此强引用self,所以不会第一时间释放,等任务结束没有了strongSelf就会继续执行dealloc
-
- 手动断联
- 手动断开联系的思想就是:使用一个临时变量,然后使用完自动置为
nil,这样也能解决循环引用问题
- 加
__block修饰的原因是,在block块中能够被修改 - 此时的
vc和self都是指向同一块内存,但是本身是两个不同指针 - 如果不置为
nil,在dealloc时,因为block持有vc,vc指向ViewControler的内存导致无法释放
-
- 自动断联(
局部变量)
- 将
block改成有参数无反@property (nonatomic, copy) void(^block)(ViewController *),然后做一些相应修改再运行
- 此时
self只是个参数,在使用完就自动释放了~
- 自动断联(
面试题
-
题目1:
static ViewController *staticSelf; - (void)testWeakStaticBlock { __weak typeof(self)weakSelf = self; staticSelf = weakSelf; }- 这个会产强引用,
staticSelf在是一个全局静态变量,weakSelf指向的是ViewController,也就是一直访问ViewController导致无法释放
- 这个会产强引用,
-
题目2:
@property (nonatomic, copy) dispatch_block_t wushuangBlock; @property (nonatomic, copy) dispatch_block_t dianJiBlock; - (void)testWeakStrongBlock { __weak typeof(self) weakSelf = self; self.wushuangBlock = ^{ __strong typeof(self) strongSelf = weakSelf; weakSelf.dianJiBlock = ^{ NSLog(@"%@", strongSelf); }; weakSelf.dianJiBlock(); }; self.wushuangBlock(); }- 这个题会导致不会释放,正常的逻辑是在
wushuangBlock中strongSelf会在回调完成销毁。 但它被dianJiBlock所持有,导致strongSelf引用计数再+1也就是2,当出作用域wushuangBlock时会进行release操作引用计数进行减1变为1,所以无法释放导致不会释放
- 这个题会导致不会释放,正常的逻辑是在
Block底层原理
- 此时对
Block有这样几个疑问:-
block的copy到底做了什么,它的原理是怎样的?
-
block捕获的变量去哪了?
-
- 怎么知道
block捕获了变量?捕获的变量又是怎么释放的?
- 怎么知道
-
block在底层编译成一个什么样的结构?
-
带着这些问题,我们去底层一探究竟
捕获分析
-
Block在C++源码结构- 先看
Block在C++中是以怎样的形态存在
int wushuangAge = 18; void (^wushuangBlock)(void) = ^{ NSLog(@" wushuang %@", wushuangAge); }; wushuangBlock();- 用
clang编译成C++后:
int wushuangAge = 18; void (*wushuangBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, wushuangAge)); ((void (*)(__block_impl *))((__block_impl *)wushuangBlock)->FuncPtr)((__block_impl *)wushuangBlock);- 在
C++中有一堆的类型强转,去掉这些后就变的比较清晰了
int wushuangAge = 18; void (*wushuangBlock)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, wushuangAge); wushuangBlock->FuncPtr(wushuangBlock);- 此时
block块被包装成__main_block_impl_0函数,它的结构如下
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int wushuangAge; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _wushuangAge, int flags=0) : wushuangAge(_wushuangAge) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int wushuangAge = __cself->wushuangAge; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_7t_93mn_rbs0p1d7bkzy4z_f0s40000gn_T_main_55fd88_mi_0, wushuangAge); }-
__main_block_impl_0是一个结构体,它有自己的构造函数,函数调用时也就是调用FuncPtr,由于参数fp是__main_block_func_0,所以函数的调用实质是调用__main_block_func_0方法,里面的wushuangAge和结构体里的wushuangAge值相同,但各自内存不同,可以理解为此处是值copy -
在结构体的成员变量中有个
wushuangAge,那么这个是不是捕获的变量有关呢?接下来去掉NSLog中wushuangAge的打印,再次编译得到:
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; } };-
没有捕获变量时,成员变量只有
impl和Desc两个,但isa类型依然是_NSConcreteStackBlock -
再捕获
对象类型查看下C++源码,然后对比捕获int类型时的__main_block_func_0的差异 -
OC代码如下
NSObject *dianjiObj = [NSObject new]; void (^wushuangBlock)(void) = ^{ NSLog(@" wushuang %@", dianjiObj); }; wushuangBlock();- 编译成
C++源码: - 此处
__main_block_func_0中的赋值是一个指针,所以此处的dianjiObj和成员dianjiObj都是指向同一片内存是一样的,另外这里的isa也是_NSConcreteStackBlock类型
- 先看
-
__block分析- 在
int变量前面添加__block:
__block int wushuangAge = 18; void (^ wushuangBlock)(void) = ^{ wushuangAge++; NSLog(@" wushuang: %d", wushuangAge); }; wushuangBlock();- 然后查看底层
C++源码:
__attribute__((__blocks__(byref))) __Block_byref_wushuangAge_0 wushuangAge = {(void*)0,(__Block_byref_wushuangAge_0 *)&wushuangAge, 0, sizeof(__Block_byref_wushuangAge_0), 18}; void (* wushuangBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_wushuangAge_0 *)&wushuangAge, 570425344)); ((void (*)(__block_impl *))((__block_impl *)wushuangBlock)->FuncPtr)((__block_impl *)wushuangBlock); // 将结构__Block_byref_wushuangAge_0 格式化下: __attribute__((__blocks__(byref))) __Block_byref_wushuangAge_0 wushuangAge = { (void*)0, (__Block_byref_wushuangAge_0 *)&wushuangAge, 0, sizeof(__Block_byref_wushuangAge_0), 18 }; // 结构体 struct __Block_byref_wushuangAge_0 { void *__isa; __Block_byref_wushuangAge_0 *__forwarding; int __flags; int __size; int wushuangAge; };- 此时出现了个
__Block_byref_wushuangAge_0结构,它是个结构体,此时的__forwarding指针指向wushuangAge,函数调用处如下:
- 此时的赋值得到的
wushuangAge指针和外面的都指向同一片内存,也就是指针拷贝,同时isa也是_NSConcreteStackBlock类型。 - 此时对以往的观点恍然大悟:
__block修饰变量的值能在block块中改变是因为它是指针拷贝;不加__block修饰,变量的改变实质是block块中临时变量在改变值,和外界的变量没有关系
- 在
对比这几种情况,于是得到以下结论:
结论:
1.block在底层是一个 结构体
2. 当捕获变量为int型时,是值copy
3. 当捕获对象类型时是指针拷贝
4. 使用__block修饰的变量在底层是指针拷贝
5.block的初始类型都是StackBlock
-
现在我们知道
Block初始类型都是StackBlock,那么在运行时是怎么变成MallocBlock的,__block做了什么导致生产了结构体__Block_byref_xxx_0,还有些上面提出的疑问还没解决,接下来我们去Block底层找答案 -
汇编分析
- 先定义一个普通
block
void (^block)(void) = ^{ NSLog(@" wushuang "); }; block();- 然后断点进去看汇编,出现一个
objc_retainBlock函数
- 继续往下面走,会走到
libobjc源码的objc_retainBlock函数
- 于是进入objc4-818.2 查看是否有相关代码:
id objc_retainBlock(id x) { return (id)_Block_copy(x); }- 源码中只返回一个
_Block_copy方法,并没有次方法的实现,只能跟着汇编继续往下跟,于是走到了libsystem_blocks.dylib
- 不过
libsystem_blocks源码并没有开源,但在 libclosure-79 找到了_Block_copy的实现
- 先定义一个普通
-
把普通的
Block改成MallocBlock类型,然后真机运行NSObject *obj = [NSObject new]; void (^block)(void) = ^{ NSLog(@" wushuang %@", obj); }; block();- 然后在进入汇编,
bl objc_retainBlock时,读x0寄存器:
- 此时的
Block类型已经变成了StackBlock,然后继续走汇编进入_Block_copy,走到ret,再次读x0寄存器:
- 可以发现经过
_Block_copy后,类型从StackBlock变成_Block_copy,那这个过程是怎样的,就需要去libclosure-79源码进行研究
- 然后在进入汇编,
变成MallocBlock
-
libclosure-79中找到_Block_copy,源码实现代码如下: -
_Block_copy主要进行了以下几个步骤的操作:-
- 根据参数
ary强转成Block_layout类型的对象aBlock,Block_layout结构如下:
struct Block_layout { void * __ptrauth_objc_isa_pointer isa; // 指向 volatile int32_t flags; // contains ref count //标识符 int32_t reserved; BlockInvokeFunction invoke; //调用函数 struct Block_descriptor_1 *descriptor; // imported variables }; - 根据参数
-
- 根据标识符判断类型,如果是释放类型则做一些相关处理;
-
- 如果是
GlobalBlock类型则直接返回对象
- 如果是
-
- 如果是
Malloc类型则会进行下面步骤创建一份:计算大小->根据内存创建新对象->拷贝一份新对象->进行相关参数赋值->isa类型变成MallocBlock->返回新对象
- 如果是
-
方法签名
- 在汇编打印时,能够发现有
signature签名,invoke回调函数等,这个signature就是block的签名v8@?0中的v代表返回值,8代表字节大小,@代表id类型,?未知,0是第0个位置,拿到签名值可以使用[NSMethodSignature signatureWithObjCTypes:"v8@?0"]查看:
- 签名中可以看到参数的个数,
Block返回值类型,签名等信息,此处又可以得出结论
结论:
Block的方法签名是:@?
-
此时又有问题了,在读
x0寄存器打印Block时,出现了copy和dispose参数,但在它的成员descriptor中没有这两个成员struct Block_descriptor_1 { uintptr_t reserved; // 8 uintptr_t size; // 8 }; #define BLOCK_DESCRIPTOR_2 1 struct Block_descriptor_2 { // requires BLOCK_HAS_COPY_DISPOSE BlockCopyFunction copy; BlockDisposeFunction dispose; }; #define BLOCK_DESCRIPTOR_3 1 struct Block_descriptor_3 { // requires BLOCK_HAS_SIGNATURE const char *signature; const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT };- 但在
Block_descriptor_2中有这两个参数,然后Block_descriptor_3中也有相关的参数signature参数,但是Block结构虽然能打印signature,copy和dispose,但结构体中并没有,这是怎么回事?其实这个是内存的连续可选
- 但在
参数可选
-
在
Block_layout结构体下面有注释imported variables(添加更多变量),那么是通过怎样来添加更多变量的呢,其实是通过BLOCK_DESCRIPTOR_2或BLOCK_DESCRIPTOR_3标识符来操作的 -
搜索
Block_descriptor_2发现并没有它是怎么生成的,但我们可以换个思路,可以找他是怎么获取的,这也就是逆向思维,于是就找到了相关函数:-
- 这里首先根据
_Block_get_descriptor函数获取Block_descriptor_1首地址:
- 这里首先根据
-
- 然后通过内存平移
Block_descriptor_1个内存大小(16字节)来获得Block_descriptor_2
- 然后通过内存平移
Block_descriptor_3的获取也一点差异,也是先获取首地址,然后平移Block_descriptor_1内存大小(16字节):- 当是
BLOCK_HAS_COPY_DISPOSE类型,就会再加上Block_descriptor_2内存大小(16字节),从而得到Block_descriptor_3 - 当不是这个类型,就会直接返回,也就是
Block_descriptor_2的位置
- 当是
-
-
此时我们可以通过
x/8gx来打印内存的分布:-
Block_layout结构的第一个参数是isa占用8字节,所以在第一段内存,此时可以打印出来是MallocBlock -
第二个参数
flags和第三个参数reserved都是4字节,由于一段内存占8字节,所以第二段内存足够存放两个参数 -
第三段内存是
invoke,和上面打印的一致 -
所以第四段肯定就是
Block_descriptor_1的内存首地址,此时可以x/8gx来打印这个地址后面内存的分布:- 第一和第二段分别代表
Block_descriptor_1的reserved和size参数 - 第三段和第四段内存地址和寄存器打印的
copy和dispose内存一致,也就是Block_descriptor_2中的内容 - 第五段内存通过打印发现是
v8@?0也就是签名,这里是Block_descriptor_3中的内容
- 第一和第二段分别代表
-
三层copy
-
现在还有捕获变量的问题没有解决,底层到底是怎么捕获的,捕获的变量什么时候释放
-
将
block添加__block修饰:__block NSObject *dianjiObj = [NSObject new]; void (^wushuangBlock)(void) = ^{ NSLog(@" wushuang %@", dianjiObj); }; wushuangBlock();-
将代码编译成
C++: -
再观察发现有两个参数
__Block_byref_id_object_copy_131和__Block_byref_id_object_dispose_131,他们的实现都差不多
- 他们的实现分别是
_Block_object_assign和_Block_object_dispose,参数里都有内存平移40的操作,那么这个平移后得到的是什么呢?那要从本身的结构去分析了
- 结构中
内存平移40后刚好是dianjiObj,所以可以断定上面传入的都是dianjiObj
-
-
再看
wushuangBlock结构体的第二个参数__main_block_desc_0_DATA,他的结构是Block_descriptor_1加上Block_descriptor_2:- 根据构造函数可以知道
copy和dispose对应的就是__main_block_copy_0和__main_block_dispose_0函数,那这个两个函数又做了什么呢?
- 搜索发现最终执行的分别是
_Block_object_assign和_Block_object_dispose,只不过此时第三个参数都是8,于是我们可以断点这两个函数肯定很重要,下面对这两个方法进行分析
- 根据构造函数可以知道
_Block_object_assign
-
在
libclosure-79源码中搜索该方法,有如下注释- 注释中说,当
Block被复制到堆中时,Block可以引用四种不同方式来提供帮助。-
- 基于c++堆栈的对象
-
- 对Objective-C对象的引用
-
- 其他模块
-
- __block变量
-
- 在这些情况下,编译器合成
辅助函数用于Block_copy和Block_release,称为copy和dispose辅助函数。对于基于c++堆栈的对象,复制助手发出对c++ const复制构造函数的调用,以及对运行时支持函数_Block_object_assign的其余调用。dispose helper对case 1(第一种情况)调用c++析构函数,对其余的调用_Block_object_dispose _Block_object_assign和_Block_object_dispose的flags参数设置为:BLOCK_FIELD_IS_OBJECT(3),捕获Objective-C对象,BLOCK_FIELD_IS_BLOCK(7),捕获另一个Block情况BLOCK_FIELD_IS_BYREF(8),捕获__block变量的情况。
- 如果
__block变量被标记为weak,编译器也会将其标记为BLOCK_FIELD_IS_WEAK (16) - 因此
Block copy/dispose帮助程序应该只生成3、7、8和24这四个标志值。 - 当
__block变量是c++对象、Objective-C对象或另一个Block时,编译器也会生成copy/dispose helper函数。与Block复制助手类似,__block复制助手(以前也称__block,byref复制助手)将执行c++复制构造函数 (而不是const构造函数!),而dispose助手将执行析构函数。类似地,helper将调用两个具有相同对象和block值的支持函数,并提供额外的BLOCK_BYREF_CALLER(128)位信息
- 注释中说,当
-
从注释中可以发现构造函数会走
_Block_object_assign方法,而析构时会走_Block_object_dispose方法 -
_Block_object_assign代码如下: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: _Block_retain_object(object); *dest = object; break; case BLOCK_FIELD_IS_BLOCK: *dest = _Block_copy(object); break; case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK: case BLOCK_FIELD_IS_BYREF: *dest = _Block_byref_copy(object); break; case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT: case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK: *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: *dest = object; break; default: break; } } -
第一次进来
flag值为8对应的就是BLOCK_FIELD_IS_BYREF,所以会执行_Block_byref_copy函数- 当
src不在堆中,会根据size进行malloc一份,也就是copy到堆,然后对copy->forwarding指向自己,src->forwarding也指向堆区的copy对象,保证指向是一样的,然后进行相关的参数赋值,最后进行(*src2->byref_keep)(copy, src)调用,byref_keep是什么呢?
struct Block_byref_2 { // requires BLOCK_BYREF_HAS_COPY_DISPOSE BlockByrefKeepFunction byref_keep; BlockByrefDestroyFunction byref_destroy; };- 根据注释得到
byref_keep实质就是C++源码中的__Block_byref_id_object_copy_131,也就是继续调用_Block_object_assign函数,且flag为131(128 + 3) - 所以此时会走进
_Block_retain_object进行引用计数+1,而持有的对象通过前面分析的就是dianjiObj,不过此时_Block_retain_object的实现并没有做任何操作,但它可以进行赋值,这一步交给了ARC去进行相关的处理。 - 所以
byref_keep方法的调用,实质就是对dianjiObj进行copy,这一步主要是对象生命周期进行保存,至此三次copy结束
- 当
三层
copy总结:
1. 如果是__block修饰的变量会通过_Block_copy方法将其从栈区拷贝到堆区
2. 第二次copy是捕获__Block_byref
3.第三次是__Block_byref对object对象进行捕获
销毁
-
现在还有个释放问题,也就是销毁问题,它主要是执行
_Block_object_dispose函数,它和上面copy有些类似void _Block_object_dispose(const void *object, const int flags) { switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) { case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK: case BLOCK_FIELD_IS_BYREF: // get rid of the __block data structure held in a Block _Block_byref_release(object); break; case BLOCK_FIELD_IS_BLOCK: _Block_release(object); break; case BLOCK_FIELD_IS_OBJECT: _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; } }- 第一次会传入
flag为8,会进入_Block_byref_release方法
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->forwarding; // 获取copy的`Block_byref` if (byref->flags & BLOCK_BYREF_NEEDS_FREE) { //释放类型 int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK; os_assert(refcount); // 引用计数进行清空处理 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); (*byref2->byref_destroy)(byref); // 调用__Block_byref_id_object_dispose_131方法 } free(byref); // 释放byref } } }- 主要是先拿到
Block_byref的forwarding,然后根据类型进行释放处理-
这里也和上面
copy类似会进行byref_destroy调用,也就是调用__Block_byref_id_object_dispose_131方法且flag=131,然后根据flag会走到_Block_object_dispose的_Block_release方法:
_Block_release方法主要是对Block_layout进行相关的处理和释放
-
- 第一次会传入
-
_Block_object_dispose的释放流程刚好和Copy相反,Copy的操作顺序是:Block_layout->Block_byref,而dispose的操作顺序是Block_byref->Block_layout,