block的分类
- 创建一个block.c文件
#include "stdio.h"
int main(){
int a = 18;
void(^block)(void) = ^{
printf("LG_Cooci - %d",a);
};
block();
return 0;
}
- 打开终端进入当前文件路径中
- 输入指令
xcrun -sdk iphonesimulator clang -S -rewrite-objc -fobjc-arc -fobjc-runtime=ios-14.4 block.c - 生成一个同名的.cpp文件
-
这段就是对应的block的代码
-
这个就是block的结构体
-
这里发现有一个
int a,这是以为这个block捕获了外界的变量才会生成的,如果没有捕获外界的成员变量,这个就是没有的。 -
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a)函数这里: a(_a)就比较奇怪这里其实就是完成了a = _a,的赋值操作。 -
isa = 是一个栈block,因为这里还是编译阶段,没有真正完成运行,所以这里的栈block是如果成为堆block的需要研究一下
-
函数的存储以及调用
-
1.一开始这里有个函数方法
__main_block_func_0 -
2.block创建时会调用方法
__main_block_impl_0把上一个函数方法作为第一个参数传入进来,参数名为pt,保存impl.FuncPtr = fp;,调用block)->FuncPtr这里就是block开始调用了刚刚存放起来的函数方法。 -
3.block调用
__main_block_func_0传入一个参数__cself这个就是block,int a = __cself->a; // bound by copy这里就是取出block中的a值进行临时变量的赋值,打印。
__block 的作用分析
-
添加__block
-
重新编译查看
发现类型发生了变化。int a 变成了
__Block_byref_a_0 *a -
这里就是取a的内存地址,完成结构体的初始化。结构体第一个值为0,第二个值为 a的内存地址,第三个为18个长度的内存大小。
-
*__forwarding存放了 a的内存地址。 -
block创建的时候把机构体
__Block_byref_a_0 a的地址传入进去进行保存 -
这里就是a = a的地址 forwarding
-
当调用block取值的时候,取出的值a就是取的地址,地址赋值后,临时变量地址和原来相同,指针赋值。
结论:__block的作用是生成了一个__Block_byref_a_0结构体让block捕获成员变量的地址值,而不是简简单单的值,放block不会变量的地址值。完成修改同一个内存地址的作用。
测试调试流程
-
写一个简单的block
-
打开汇编调试
-
发现这里进入了objc_retainBlock
-
进入的时block_copy
底层源码查看 libclosure-79
- 1.block的底层结构是这个结构体
- 1.isa 是 isa的指向表明block的类型
- 2.flags是一个标识
- 3.reserver是流程数据
- 4.invoke 存放调用函数
- 5.descriptor 其他的相关的描述
- 判断是否释放,判断是否为全局block,就返回了否则进入下一步。
_Block_copyblock的创建编译时是一个栈block,只有通过 block_copy操作后才会成为一个堆block.到运行时发现捕获了外界变量,就会根据需要捕获的内存大小
申请一段内存空间,拷贝栈block中的所有内容,成为一个堆block
这里重新标记位堆block
-
Block_descriptor_1 *descriptor;
这是一段内存连续的可选参数,block类型不一样,这里的数据结构也会不一样。
-
如果类型是 Block_descriptor_2 就会有copy和dispose两个函数方法 Function
如何成为Block_descriptor_2
-
发现获取Block_descriptor_2是通过内存平移Block_descriptor_1大小获得
-
Block_descriptor_3 也是同理的,
-
案例图片介绍
cpp分析整个流程
-
写一个简单的block在viewcontroller.
-
通过终端变成cpp文件后,开始分析这个block的运行过程
-
创建block
-
生成了这样的一个结构体的block
-
看desc,发现这个内存空间已经在创建block的时候赋值给他了
-
copy函数和dispose函数
copy函数真实调用了_Block_object_assign函数,
-
捕获变量的相关处理,即捕获了不同类型的成员变量,这里做了不一样的处理
-
BLOCK_FIELD_IS_OBJECT:普通对象类型,就
BLOCK_FIELD_IS_OBJECTarc系统函数处理后,地址指针的赋值。 -
BLOCK_FIELD_IS_BLOCK:block类型,就会做block_copy操作,把这个block对象的地址指针赋值保存。
这里是__weak,__block,修饰的变量,做_Block_byref_copy操作
- 如果不是一个正常的对象,引用计数+1,
copy->forwarding = copy;
src->forwarding = copy;改变原来捕获变量forwarding和我block中存放的变量的forwarding相同,所以两个是一个东西。
- __Block_byref_objc1_0 这个被__block修饰的对象最后转变的结构体是这个类型
- 查看cpp文件发现这个结构体是这样的
- isa = 8字节
- forwarding 8
- flags 4
- size 4
- 两个Function 8 + 8
- objc1
这里创建出结构体后,byref_keep = 就是__Block_byref_id_object_copy_131 这个函数
这里会再次调用block_objc_assign
就是在这里调用的,+40 个字节平移刚刚好就是
__Block_byref_objc1_0结构体中的 objc1
在递归一次这里的调用。
所以得出结论 block捕获变量会根据不同类型来进行不同的操作。
block的释放就是byref_destroy也是根据不同类型进行不同的操作释放。