block5-block内存管理
block 内存管理
测试代码:
1 | NSObject * object = [[NSObject alloc] init]; |
查看 cpp 文件代码:
1 | struct __Block_byref_blockObject_0 { |
分析上面的代码可以看出,对于非 __block 修饰的 auto 变量 object 内存管理和我们 前面 讲过的 copy 过程一样:
持有:
1、block 内部调用 desp 里面的 copy 函数
2、copy 函数会调用_Block_object_assign
3、_Block_object_assign
函数会根据 auto 变量修饰符的类型(__strong
、__weak
、__unsafe_unretained
)做出相应的操作,形成强引用或弱引用
销毁:
1、block 内部调用 得搜 里面的 dispose 函数
2、dispose 函数会调用_Block_object_dispose
3、_Block_object_dispose
函数会释放引用的 auto 变量
上面就是非 __block 修饰的对象类型的 auto 变量内存管理。
下面来分析下 __block 修饰的对象类型的 auto 变量内存管理:
上一节 讲到了 __block 的原理,可以想到 blockObject 被包装成了一个对象,而 \_\_Block\_byref\_blockObject\_0
里面的 NSObject *\_\_strong blockObject;
就是最终被捕获的变量,可以看到这里的修饰符是 __strong,根据前面讲过的 copy 原理猜想一下这里的捕获结果可能和被修饰的 auto 变量的强弱有关,来做一个测试:
1 | void (^block)(void); |
查看打印结果,Animal 是在作用域结束后就释放了,而此时 block 还没有被释放,看下 cpp 文件的代码:
1 | struct __Block_byref_weakObject_0 { |
说明捕获到的确实是 __weak 的类型。
再来看一下上面 blockObject 被捕获对象初始化的地方,这里多了两个函数:__Block_byref_id_object_copy_131
、__Block_byref_id_object_dispose_131
,来看一下这两个函数的实现:
1 | static void __Block_byref_id_object_copy_131(void *dst, void *src) { |
这两个方法是不是很熟悉,跟我们前面讲 copy 的时候调用的方法是一样,看下 _Block_object_assign 函数的参数:(char*)dst + 40
,这里的 dst 就是 __Block_byref_blockObject_0
,那么 +40 代表什么呢?看下 __Block_byref_blockObject_0
的结构:
1 | struct __Block_byref_blockObject_0 { |
+40 的地址正好是 blockObject 的地址,所以这两个函数是用来管理 blockObject 的。
总结下 __block 修饰变量的内存管理:
持有:
1、blockObject 被包装成
__Block_byref_blockObject_0
对象,__Block_byref_blockObject_0
对象里面持有 blockObject(blockObject 的强弱是根据自身的修饰符决定的)
2、__main_block_impl_0
持有__Block_byref_blockObject_0
3、__Block_byref_blockObject_0
的内存管理依赖__main_block_impl_0
的 desp 的 copy 函数(_Block_object_assign
)
4、blockObject 对象的内存管理依赖__Block_byref_blockObject_0
的 copy 函数(_Block_object_assign
)
销毁:
1、
__main_block_impl_0
通过 desp 的 dispose 函数释放__Block_byref_blockObject_0
(_Block_object_dispose
)
2、__Block_byref_blockObject_0
通过调用内部的__Block_byref_blockObject_0
函数释放最终的 blockObject 对象
结论总结
- 当 block 在栈上时,对对象类型的 auto 变量、__block 变量不会产生强引用
- 当 block 被 copy 到堆上面时会通过 copy 函数来处理 (__block 变量: _Block_object_assign((void)&dst->a, (void)src->a, 8/BLOCK_FIELD_IS_BYREF/), auto 变量:_Block_object_assign((void)&dst->p, (void)src->p, 3/BLOCK_FIELD_IS_OBJECT/))
- _Block_object_assign函数会根据所指向对象的修饰符(strong、weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain)
- 当 block 从堆上移除时会调用 dispose 函数处理(__block 变量:_Block_object_dispose((void)src->a, 8/BLOCK_FIELD_IS_BYREF/), auto 变量:_Block_object_dispose((void)src->p, 3/BLOCK_FIELD_IS_OBJECT/))
- _Block_object_dispose函数会自动释放指向的对象(release)
block 的 forwarding 指针
上面 blockObject 的访问其实是:blockObject->__forwarding->blockObject,为什么这里要多一步 __forwarding 的操作呢,而且 __forwarding 还是指向自己的。
因为当栈上的 block copy 到堆上时,block 实际访问的其实是堆上的内存,如果这时再访问栈内存是不正确的,所以此时栈上的 __forwarding 指针会指向堆上面的内存,当访问 blockObject 的时候其实访问的是最终被 copy 到堆上面的内存,这样就不会出现访问错乱的问题。
可以来验证一下,使用 MRC 的环境:
1 | NSObject * object = [[NSObject alloc] init]; |
此时 block 是在栈上面,来打印下此时的地址:(__Block_byref_blockObject_0 *) __forwarding = 0x00007ffeefbff468
当调用完 copy 函数之后:(__Block_byref_blockObject_0 *) __forwarding = 0x000000010053af80
,很明显,此时的地址从栈上指向了堆空间。