前言
通过block上篇的学习,在上层我们已经知道了block的三种表现形式和关于引用计数以及循环引用相关的理解,通过这篇将进一步去了解他底层的实现,内部到底是什么结构?怎么去copy,如何捕获保存以及释放?
cpp文件分析
block在如果有外部变量进来的时候,会自从生成相应的成员变量并且给内部的成员变量赋值。
无成员变量时
去掉强转类型以后的代码,第一步函数声明,第二步,函数式调用保存。也就是说,如果函数不调用,是不会保存的。
看一下具体的函数实现,可以看到编译阶段的block是栈block
,而我没有进行弱引用访问了外部的变量,应该是堆block
才对,猜测应该是在运行
阶段作了处理。
如果要对外界的变量作修改,需要加__block
,否则,内部生成的变量a与外界变量a不是同一个地址。那么加了__block
有什么作用呢?
生成了一个a,类型是__Block_byref_a_0
__Block_byref_a_0
是一个结构体。
函数声明里用的就是这个结构体本身的地址。
所以在进行值修改的时候,这里修改的就是他自己的值,修改的是同一片内存空间。
源码分析
找到源码工程
通过打断点。
找到是在libSystem,但是并没有开源,但是不代表找不到,可以在libclosure-79中找到源码。
找到block_copy
,如果是释放状态或者全局都是直接对block返回,如果是栈block,那么就需要copy
一份,因为在编译器作开辟空间的操作,会加大了编译器的负担。然后全部拷贝
一份,isa指向变成堆block
。
看一下block_layout
结构,看一下主要的几个参数表示
isa
指向,是栈block还是堆block还是全局block。
flags
标识符
invoke
函数调用
为何栈block会变成堆block?
之前看到,本来是堆block在编译时进来是栈block,那么做了什么操作呢?通过寄存器先看一下copy前后的打印。
v8@?0
的意思可以通过打印签名[NSMethodSignature signatureWithObjCTypes:"v8@?0"]
。
number of arguments
当前参数1个,返回空。
flags
block类型的对象
memory
8个字节。
为什么要签名?
因为在invoke的时候,他也是 一种消息发送的机制,这种消息发送跟之前的消息转发是一样的,最后一步也会走到签名,关于方法生效或者异常的情况,所以需要签名。
Block_descriptor
看一下正常的情况如果使用了外部的变量。会存在copy
和dispose
,这些都记录在Block_descriptor1
中,根据情况不同,展示的也不同,具体是通过内存平移的方式去获得Block_descriptor2
或者Block_descriptor3
.因为此处的内存是连续
的,所以可以平移获取。
__block指针拷贝的补充
如果只是普通的对象进来,那么内部生成成员变量跟外界的捕获的成员变量不是同一个,虽然他们指向同一片空间,值相同。如果是__block
修饰的对象,并且满足BLOCK_BYREF_HAS_COPY_DISPOSE
,也就是捕获到了外界的变量,开辟了堆内存并且copy,拿到了变量以后,通过byref_keep
对整个变量生命周期的持有,防止当变量释放,整个block也释放了。
**struct** Block_byref_2 {
// requires BLOCK_BYREF_HAS_COPY_DISPOSE
BlockByrefKeepFunction byref_keep; //= __Block_byref_id_object_copy_131
BlockByrefDestroyFunction byref_destroy; // = __Block_byref_id_object_dispose_131
};
block对外部变量的捕获流程。
分析捕获变量的情况,主要看三种。
第一种是object
对象类型,直接指针指向,第二种是block
类型,对本身block的捕获,第三种byref
类型。
官方注释说明。
如果是__block修饰的变量,再被捕获进来以后,除了正常的进行block从栈到堆的copy以外,还会生成相应的__Block_byref_id_object_copy
方法(第二层拷贝),这个方法里会在走_Block_object_assign
方法在进行一次拷贝(第三层拷贝)。
Block内部结构的一些结构体定义解释整理
Block_layout
结构体成员含义如下:
-
isa: 指向所属类的指针,也就是block的类型
-
flags: 标志变量,在实现block的内部操作时会用到
-
Reserved: 保留变量
-
invoke: block执行时调用的函数指针,block内部的执行代码都在这个函数中
-
descriptor: block的详细描述,包含 copy/dispose 函数,处理block引用外部变量时使用
-
variables: block范围外的变量,如果block没有调用任何外部变量,该变量就不存在
Block_descriptor
结构体成员含义如下:
- reserved: 保留变量
- size: block的内存大小
- copy: 拷贝block中被
__block
修饰的外部变量 - dispose: 和
copy
方法配置应用,用来释放资源
被__block
修饰的变量被封装成了一个对象
__Block_byref_a_0
成员含义如下:
- __isa: 指向所属类的指针
- __forwarding: 指向对象在堆中的拷贝
- __flags: 标志变量,在实现block的内部操作时会用到
- __size: 对象的内存大小
- a: 原始类型的变量