上一篇文章 介绍了如何获取 NSTimer(FBObjectiveCNSCFTimer
)的所有强引用对象。本文继续介绍如何获取一种特殊OC对象 – Block
的所有强引用对象。
在 FBRetainCycleDetector 的源码中,Block
对应的类为 FBObjectiveCBlock
。
FBRetainCycleDetector 源码地址: https://github.com/facebook/FBRetainCycleDetector
Block 基础
OC 的 Block 其实也是一个结构体,结构如下:
1 | struct BlockLiteral { |
其中,isa 这个字段大家应该都很熟悉,和 objc_class
结构体里的 isa 一样,都是指向具体的类。Block 结构体对应的类可能是以下三种中的任意一种,大家可以自行了解,本文不再继续展开介绍。
- __NSGlobalBlock
- __NSMallocBlock
- __NSStackBlock
FBRetainCycleDetector 源码中 Block 对象对应的类为 FBObjectiveCBlock
。
1 | /** |
如何判断一个对象是 Block
如果是普通的OC对象,我们可以直接用 [objc class] 获取其类型。 Block 对象能不能也通过这种方式来判断呢?当然是可以的,Block 对象是一种特殊的 OC 对象 ,再怎么特殊,它也是一个OC对象,普通OC对象能做的事情,它也能做。
问题的关键是,OC Runtime 并没有直接暴露出所谓的 Block 类 。上面提到过,一个 Block 可能是 __NSGlobalBlock
,__NSMallocBlock
,__NSStackBlock
中的任意一种,但是这三个类都没有在OCRuntime 中直接暴露。
Block 的运行时类型
所以,我们首先要找到一个 Block 对象在运行时到底是什么类。
FBRetainCycleDetector 通过创建一个 testBlock 并不断向上查找其 superclass ,来获取 Block 对象在运行时的 “基类”。
1 | static Class _BlockClass() { |
注意: _BlockClass 方法在查找 superclass 时,如果找到了
NSObject
,就直接终止查找过程。否则找到的基类就都成了NSObect
。
实际运行起来,大家会发现这个方法的返回值为 NSBlock
。其声明如下:
1 | @interface NSBlock : NSObject <NSCopying> |
这再次印证了我们上面说过的,“Block 对象是一种特殊的OC对象”。Block 对象都继承自 NSBlock
,NSBlock
又是 NSObject
的子类。
Block 类型判断
1 | BOOL FBObjectIsBlock(void *object) { |
这段代码就很简单了,直接判断当前对象的 Class 是不是 NSBlock
类的 subclass 即可。
获取 Block 的强引用对象
再回头看 Block 结构体的最后,有一行注释:
// imported variables
1 | struct BlockLiteral { |
Block 引用的对象会存放在 Block 结构体的最下方,这里似乎是一个突破口,直接遍历 imported variables 是不是就可以拿到所有的强引用对象了呢?
在 普通OC对象的所有强引用对象 一文中,我们提到引用对象分为 强引用对象 和 弱引用对象 ,对于 Block 来说也是一样。
对于普通OC对象,我们可以通过 class_getIvarLayout 来区分强弱引用对象,但是 Block 对象并没有直接提供 class_getIvarLayout 方法,所以我们需要换一种方式获取 ivarLayout 。
dispose_helper
继续看 Block 结构体,其有一个 BlockDescriptor
结构体,结构如下:
1 | struct BlockDescriptor { |
其中,size 字段表示 Block 持有的所有对象的大小,通过这个字段我们能拿到 Block 持有的对象总数(包括强引用和弱引用)。
在这个结构体中还有两个函数指针, copy_helper 和 dispose_helper 。 copy_helper 用于 Block 的拷贝,而 dispose_helper 用于 Block 的析构。
Block 在析构时会调用 dispose_helper ,对所有强引用对象发送 release 消息,以销毁强引用对象。弱引用对象此时自然不会收到 release 消息。
FBRetainCycleDetector 正是利用这个原理,来获取 Block 的所有强引用对象。
发送/接收 release 消息
Block 在析构时会调用 dispose_helper ,对所有强引用对象发送 release 消息。那么我们只需要手动触发一次 dispose_helper 方法,看看哪些对象收到了 release 消息,这些对象就是被强引用的对象了。
问题来了,怎么知道哪个对象执行了 release 方法呢?难道要把所有对象的 release 方法都 hook 掉?这样肯定是不现实的。而且,我们只是想知道哪些对象被强引用了,并不想把这些对象真正销毁掉。如果真销毁掉了,整个 Block 肯定乱成一锅粥了😂。
因此,我们要创建一个 虚假的对象 ,用来接收 release 消息。在 FBRetainCycleDetector 的源码中,FBBlockStrongRelationDetector
就是这个 虚假的对象 。
1 | @implementation FBBlockStrongRelationDetector |
FBBlockStrongRelationDetector
对象在接收到 release 消息时,只是将 _strong 标记为 YES
,真正的销毁是在 trueRelease 方法中完成的。
收集强引用对象
通过上面的分析,我们已经能够:
- 通过解析 Block 结构体数据和
BlockDescriptor
结构体的 size 字段,获取到所有引用对象列表。 - 通过手动执行
BlockDescriptor
结构体的 dispose_helper ,获取所有强引用对象在对象列表中的位置。
那最后一步就很明显,也很简单了:
- 收集所有强引用对象。
其实,查找 Block 强引用对象和查找普通OC对象的强引用对象,在思想上还是非常相似的。都需要先获得所有对象列表,再筛选出强引用对象。只不过普通OC对象筛选强引用对象非常方便,可以直接获取到 ivarLayout ,Block 对象要麻烦很多。
源码解读
FBObjectiveCBlock
类中提供了如何获取 Block 对象的所有强引用对象的源码:
1 | - (NSSet *)allRetainedObjects { |
其中, FBGetBlockStrongReferences 方法的源码在 FBBlockStrongLayout.m 中。
1 | // 有删减 |
总结
FBRetainCycleDetector 将OC对象划分为3类,分别为:
- FBObjectiveCObject
- FBObjectiveCNSCFTimer
- FBObjectiveCBlock
本系列的三篇文章分别介绍了对这3类对象如何获取其所有强引用对象。汇总起来,就是 FBRetainCycleDetector 查找OC对象的所有强引用对象的基本原理。知道每个OC对象的所有强引用对象后,就能够生成每一个对象的引用关系图,继续按图索骥,就能够检测到是否存在循环引用链。
FBRetainCycleDetector 是一个非常优秀的开源项目,尤其适合用来学习 OC Runtime 知识,了解 OC 对象在运行时的结构,顺便掌握一些 “黑科技” ,强烈建议大家阅读其源码。
前两篇文章链接: