iOS底层探索之Block(四)——Block的探索和源码分析
Block的本质是什么吗?__Block底层又做了什么呢?
在上一篇博客中,已经探索到block
的本质是结构体(__main_block_impl_0
)继承自__block_impl
,block
可以捕获外部变量,通过__block
修饰内部可以变更外部变量的值。 那么本篇博客将对继续对block
的底层原理进行分析。
iOS底层探索之Block(一)——初识Block(你知道几种Block呢?)
iOS底层探索之Block(二)——如何解决Block循环引用问题?
1. block追根溯源
在以往的分析都是找到分析对象的出处,然后看相应的源码进行分析,那么block
是来自哪个库呢,现在还不得而知,我们现在尝试的去寻找一下。
汇编查看流程
block代码举例通过简单的
block
代码去查看汇编的调用情况,是否会有不一样的发现呢!
objc_retainBlock
从👆上图汇编的流程中可以发现,调用了一个
objc_retainBlock
,objc
开头的不就是libobjc.A.dylib
源码库嘛!那么我再去验证一下,通过符号断点看看,到底是不是libobjc.A.dylib
。
下符号断点验证
下符号断点符号断点跟踪通过符号断点,也验证了确实是来自我们熟悉的
libobjc.A.dylib
源码库,如下所示:
再次跑一次代码,确实走到了下的符号断点处,也发现了是来自
libobjc.A.dylib
,验证了上面的猜想,然后jmp
跳转到_Block_copy
,源码中也可以验证:objc_retainBlock
搜索_Block_copy从源码中可以知道调用
objc_retainBlock
返回的是_Block_copy
,但是在源码中并没有搜索到_Block_copy
的方法实现在哪里。
既然源码中没有
_Block_copy
的实现,大胆猜测一下,是不是不在libobjc.A.dylib
里面呢?那么去下_Block_copy
符号断点看看不就知道了啊!如下:
_Block_copy符号断点
通过下
_Block_copy
符号断点的跟踪,发现_Block_copy
是来自于libsystem_blocks.dylib
这个库,但是这个libsystem_blocks.dylib
并没有开源,这一波操作就很烦了。那该怎么办呢?这里有两种办法,我们已经知道是来自libsystem_blocks.dylib
就可以进行反汇编,还有一种就是找libclosure
来代替,也是可以的。libclosure源码工程
Block_layout在
libclosure-79
的工程中搜索_Block_copy
是可以找到的,来自于Block_layout
的结构体,是在Block_private.h
文件中。
Block_layout
结构体里面有 isa
、标记flags
、invoke
函数、descriptor
描述等。
在clang
获取的cpp
文件中也可以看到block
源码的出处,来自Block_private.h
,如下图所示:
对比图通过对比还发现,在
cpp
文件中block
定义的结构体__block_impl
和源码中Block_layout
的结构体是一致的,如下图所示:
小结
:通过汇编调试,下符号断点,最终追根溯源到block
是来自于libsystem_blocks.dylib
,但是其并没有开源,可以通过对libsystem_blocks.dylib
进行反汇编或者通过libclosure
来代替源码工程来进行源码分析。
2. 汇编查看block捕获变量前后变化
block
捕获外部变量,在编译时是栈block
,在运行时会copy
到堆区
,变成堆block
。
变化前
下面就来分析这种内存变化是何时发生的,如下图所示: 读寄存器看 block类型变化通过汇编调试,读取寄存器,发现当调用
objc_retainBlock
时,读取寄存器x0
这里是模拟器就是rax
,分析block
的数据状态还是在栈区
的,那么继续往下走流程,看看调用_Block_copy
之后是有什么样的变化。
变化后
继续走,当调用_Block_copy
之后变化如下:
当调用
_Block_copy
之后变化的变化是,从栈区(NSStackBlock)
变成堆区(NSMallocBlock)
的 block
了,地址发生了改变,从栈区拷贝到了堆区。
这也就验证了 block
捕获了外部变量,在编译时是栈block
,在运行时通过_Block_copy
会copy
到堆区,变成堆block
。
3. _Block_copy源码分析
上面已经定位到 block
的源码了,那么具体看看源码吧
- Block_layout
truct Block_layout {
void * __ptrauth_objc_isa_pointer isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
BlockInvokeFunction invoke;
struct Block_descriptor_1 *descriptor;
// imported variables
};
-
isa isa
指向确定block
类型 -
flags
标识码 -
reserved
保留字段 -
invoke
函数,也就是FuncPtr
-
descriptor
相关附加信息
BLOCK_DESCRIPTOR -
flags
在这里插入图片描述 -
_Block_copy源码分析
_Block_copy源码分析
- 对
flags
也就是引用计数进行判断,如果是BLOCK_NEEDS_FREE
已经释放了,直接返回aBlock
- 是否是全局的
block
,也是直接返回aBlock
- 如果不是全局的那么就是栈
block
或者是堆block
,但是此时是编译期不可能是堆区的block
,如果编译期就开辟内存,对编译器压力太大了。所以编译器就标记为栈 block,当编译器知道你捕获到外部变量,到运行时
就进行相关的内存开辟操作(malloc
),在进行memmove
拷贝一份 - 对其他一些信息,包括
invoke
、签名(ptrauth_signed_block_descriptors
)信息也包装进result
- 最后
isa = _NSConcreteMallocBlock
返回一堆区的block
在上面汇编查看的时候,打印了捕获变量的前后变化,lldb
调试打印信息中有signature
、 invoke
、 copy
、 dispose
等信息,这些是什么呢?
这个
signature
就是签名,还记得消息转发的时候这种[NSMethodSignature signatureWithObjCTypes:"v8@?0"];
代码吗?
这是Type Encodings,类型编码。iOS提供了一个叫@encode
的指令,可以将具体的类型表示成字符串编码!在分析类的结构的时候也介绍过。
-
v
表示viod
,无返回值 -
8
表示占了8
个字节 -
@
表示 参数id self
-
?
未知类型(用于函数指针) -
0
表示id
从0
号位开始
从下图中也可以看出这些信息,如下:
signature
在控制台可以通过 po 打印
po [NSMethodSignature signatureWithObjCTypes:"v8@?0"]
查看signature
具体信息。
还记得上面介绍了flags
和 descriptor
相关附加信息吗
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
uintptr_t reserved;
uintptr_t size;
};
在Block_descriptor_1
里面reserved
就是保留字段,size
为block
的大小。
如果#define BLOCK_DESCRIPTOR_2 1
,也就是为Block_descriptor_2
的时候,才有上面控制台打印的copy
和dispose
。
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
BlockCopyFunction copy;
BlockDisposeFunction dispose;
};
Block_descriptor_3
是可选的参数。而这里就通过flag
字段来判断block
是否存在Block_descriptor_3
的相关属性
#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
// requires BLOCK_HAS_SIGNATURE
const char *signature;
const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
Block_descriptor的get方法
- 通过
Block_descriptor
的get
方法可以发现,Block_descriptor_2
可以通过Block_descriptor_1
地址平移的方式获取 - 获取
Block_descriptor_3
时会判断Block_descriptor_2
是否存在,如果不存在,就不需要添加Block_descriptor_2
的地址空间。
地址平移
lldb
调试验证,如下
更多内容持续更新
🌹 喜欢就点个赞吧👍🌹
🌹 觉得有收获的,可以来一波,收藏+关注,评论 + 转发,以免你下次找不到我😁🌹
🌹欢迎大家留言交流,批评指正,互相学习😁,提升自我🌹