这是我参与8月更文挑战的第30天,活动详情查看: 8月更文挑战
汇编分析block流程
在上一篇文章中我们分析了block的底层结构,接下里我们分析一下block的执行流程;
我们新建一个iOS工程,然后编写如下代码:
在block的地方打上断点,然后运行项目,打开汇编界面:
在汇编代码中,我们定位到了objc_retainBlock这样一个符号;我们添加符号断点objc_retainBlock,继续执行代码:
发现接下来,将会调用_Block_copy,继续添加符号断点_Block_copy,向下执行代码:
发现_Block_copy来自于libsystem_blocks.dylib,同样的调用我们从objc的源码中也可以分析得到:
定位到_Block_copy之后,我们无法继续向下分析,因为libsystem_blocks没有开源;但是我们可以从libclosure的源码中找到_Block_copy的实现:
在_Block_copy中,定义了一个结构体struct Block_layout *aBlock;,其结构如下:
- 结构体中有个
isa的成员变量,(指向栈block或者堆block) flags标识码,里边可以存储一些数据信息;invoke是调用函数;descriptor相关描述信息(比如是否有有析构函数等等信息)
此时,我们打印一下寄存器中的数据:
x0是消息接收者,它是一个__NSGlobalBlock__,因为目前我们的block没有捕获任何的外部变量;
接下来我们将block代码修改如下:
继续执行代码到_Block_copy的符号断点,我们打印一下x0寄存器的数据:
- 因为此时
block捕获了外部变量,所以他此时是一个__NSStackBlock__,是个栈block;但是按照我们之前分析的结果,block捕获了外部变量,并且是强引用,它应该是一个堆block才对啊,这里为什么变成了栈block了,前后矛盾???
但是需要注意的是,此时我们的程序在断点过程中,_Block_copy还没有执行完毕!
那么我们在_Block_copy最后return的地方打上断点,向下执行,我么你看一下return时x0寄存器中的情况:
_Block_copy方法开始执行时,与执行完毕之后return时,x0寄存器地址发生了变化,并且从__NSStackBlock__变成了__NSMallocBlock__;_Block_copy方法将一个栈block变成了堆block;
我们现在从汇编角度分析出了block的变化,接下来我们从源码中分析验证一下;
源码分析block流程
_Block_copy的源码实现如下:
解析:
(aBlock->flags & BLOCK_NEEDS_FREE)如果block被标识为释放的,那么直接return;(aBlock->flags & BLOCK_IS_GLOBAL)如果block被标识为全局的,那么直接return;size_t size = Block_size(aBlock);获取block的大小;struct Block_layout *result = (struct Block_layout *)malloc(size)按照大小开辟内存空间;memmove(result, aBlock, size)将aBlock拷贝到result中;result->isa = _NSConcreteMallocBlock将block重新标记为堆block;
编译期生成的只能是
栈block,因为在编译期没有alloc等操作,没有内存的开辟,所以只能标记为栈block,然后在运行时重新将栈block标记为堆block;
我们仔细查看控制台打印信息的时候发现,除了打印出以上内容之后,还打印出了signature: "v8@?0"和invoke,copy,dispose这些信息;那么他们都是什么东西呢?
signature是我们当前block的签名; 我们可以通过下面的方式来查看这个签名信息:
-
解析
number of arguments = 1:传递了1个参数;is special struct return? NO:没有返回值;argument 0::参数从0号位置开始;flags {isObject, isBlock}:是一个block,也是一个对象,也就是一个block类型的对象;
-
invoke是函数的调用者的指针;
那么copy和dispose对block有什么影响呢?我们下次继续讲解......