Block 知识点
关于 Block 的一些知识点
Block 实际结构
先看一段示例
1 | int age = 10; |
将该段代码,反编译为 C++ 看看内部实现
1 | xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m |
将生成的代码关键部分简化一下方便阅读
1 | int age = 10; |
实际生成一个__main_block_impl_0
的结构体
名称__x_block_impl_y
,x 为文件名,y 为 block 的编号(0、1、2…)
1 | struct __main_block_impl_0 { |
关于 Block 的变量捕获
block 实际属于跨函数调用,为了保证 block 内部能够正常访问外部的变量,block 有个变量捕获机制。
变量类型 | 是否捕获到 block内部 | 访问方式 | |
局部变量 | auto | √ | 值传递 |
static | √ | 指针传递 | |
全局变量 | × | 直接访问 |
Block 的类型
block 有三种类型,通过调用 class 方法或 isa 指针查看具体类型,最终都是继承自 NSObject
__NSGlobalBlock__
(_NSConcreteGlobalBlock)__NSStackBlock__
(_NSConcreteStackBlock)__NSMallocBlock__
(_NSConcreteMallocBlock)
block 类型 | 环境 |
---|---|
__NSGlobalBlock__ |
没有访问 auto 变量 |
__NSStackBlock__ |
访问了 auto 变量 |
__NSMallocBlock__ |
__NSStackBlock__ 调用了 copy |
Block copy
每一种类型 block 调用了 copy 后的结果
block 的类型 | copy 后的存储区域 | copy 效果 |
---|---|---|
_NSConcreteGlobalBlock |
程序的数据区域 | 什么也不做 |
_NSConcreteStackBlock |
栈 | 从栈区复制到堆 |
_NSConcreteMallocBlock |
堆 | 引用计数增加 |
在 ARC 环境下,编译器会根据情况自动将栈上的 block 复制到堆上,如下
- block 作为函数返回值时
- 将 block 赋值给
__strong
指针时 - block 作为 Cocoa api 中方法名含有 usingBlock 的方法参数时
- block 作为 GCD api 中的方法参数时
所以 MRC 下 block 作为属性时建议使用 copy 修饰符修饰;ARC 下可以用 strong,也可以延续 MRC 下的写法用 copy,都不影响,因为编译器会根据情况自动操作
对象类型的 auto 变量
栈空间内的 block,不会强引用外部的 auto 变量。
如果 block 被 copy 到堆上
- 会调用 block 内部的 copy 函数
- copy 函数内部会调用
_Block_object_assign
函数 _Block_object_assign
函数会根据 auto 变量的修饰符(__strong、__weak、__unsafe_unreatained)做出相应操作,形成层强引用(retain)或弱引用
如果 block 从堆上移除
- 会调用 block 内部的 dispose 函数
- dispose 函数内部会调用
_Block_object_dispose
函数 _Block_object_dispose
函数会自动释放引用的 auto 变量(release)
函数 | 调用时机 |
---|---|
copy 函数 | 栈上的 block 复制到堆时 |
dispose | 堆上的 block 被废弃时 |
看一下,block 内引用对象时,block 内部结构变化
1 | Person *obj = [[Person alloc] init]; |
编译为 C++ 代码后,相比引用基本数据类型会有如下变化。
1 | static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);} |
__block 修饰符
__block
用于解决 block 内部无法修改 auto 变量的问题,不能用于修改全局变量、静态变量(static)。
1 | __block int age = 10; |
编译为 C++ 代码后,会有以下片段,__block
变量会被包成一个 __Block_byref_age_0
对象,所以在__main_block_desc_0
也会存在 copy
和dispose
用于内存管理。
1 | struct __Block_byref_age_0 { |
__main_block_func_0
函数内的实现
1 | static void __main_block_func_0(struct __main_block_impl_0 *__cself) { |
__block 内存管理
- 当 block 位于栈上时,不会对
__block
变量产生强引用 - 当 block copy 到堆上时
- 会调用 block 内部的 copy 函数
- copy 函数调用
_Block_object_assign
函数 _Block_object_assign
会对__block
变量形成强引用(retain 一次)
block 复制到堆上时,会将内部引用的__block
变量一起拷贝到堆上。
因为__Block_byref_age_0
是一个对象,而__main_block_impl_0
引用了这个对象,所以需要该对象的内存管理操作。
- 当 block 从堆上移除时
- 会调用 block 内部的 dispose 函数
- dispose 函数会调用
_Block_object_dispose
函数 _Block_object_dispose
函数会自动释放引用的__block
变量(release 一次)
ARC 下会对强引用的 block,自动复制到堆上,block 对
__block
变量会自动强引用;对普通的 auto 对象,会根据是__strong
或是__weak
形成强引用或者弱引用。
MRC 下将 block copy 到堆上,block 不会对__block
变量强引用。
对象类型的 auto变量、__block 变量
- block 位于栈上时,对他们都不会产生强引用
- block 位于堆上时,都会调用 copy 函数来处理他们
__block
变量1
_Block_object_assign((void*)&dst->a, (void*)src->a, *BLOCK_FIELD_IS_BYREF*/);
对象类型的 auto 变量
1
_Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);
- block 从堆上移除时,都会调用 dispose 函数来处理他们
__block
变量1
2_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
对象类型的 auto 变量
1
2_Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);
循环引用问题
ARC 下可以使用 __weak
__unsafe_unretained
解决,也可以使用 __block
解决,block 内将引用的对象置为 nil,但是必须调用 block。
MRC 下可以使用 __unsafe_unreatined
,__block
解决