FBRetainCycleDetector解析——获取一般对象的Strong成员变量

Facebook 前不久发布三个工具用于检测内存泄露,分别是 FBRetainCycleDetectorFBAllocationTrackerFBMemoryProfiler,并在他们的技术博客上发布了一篇相关的博客 Automatic memory leak detection on iOS

看了一下 FBRetainCycleDetector 的代码,通过 Runtime 捕获循环引用,原理在 Automatic memory leak detection on iOS 这篇博客中有说到:把对象(包括 Block 对象)当成节点,以强引用为关系建立有向图,以深度优先遍历该有向图,寻找有向图中的环,一个环就代表一个循环引用。

本篇博客介绍 FBRetainCycleDetector 库中获取一般对象的 Strong 成员变量的原理。

Runtime 中的相关函数

先介绍一下 Runtime 中的几个函数:

  • Class object_getClass(id obj):获取一个对象所属的类;
  • Class class_getSuperclass(Class cls):获取一个类的父类;
  • Ivar *class_copyIvarList(Class cls, unsigned int *outCount):获取某个类中的成员变量个数及成员变量数组,但是不包含父类的;
  • ptrdiff_t ivar_getOffset(Ivar v):获取成员变量中的偏移位置,关于成员变量偏移位置的解析,可以看这篇博客Objective-C类成员变量深度剖析
  • const uint8_t *class_getIvarLayout(Class cls):获取某个类中 Strong 类型的成员变量的布局,关于 Ivar Layout 下面会展开说明一下。

Ivar Layout

const uint8_t *class_getIvarLayout(Class cls) 函数中有一个 Ivar Layout 的概念,表示成员变量的布局情况。

相对于 Strong 类型的成员变量,函数 const uint8_t *class_getWeakIvarLayout(Class cls) 可以获取 Weak 类型的成员变量的布局情况。

例子如下:

@interface ClassA : NSObject
{
__unsafe_unretained id ivar0;
__weak id ivar1;
__strong id ivar2;
__weak id ivar3;
__strong id ivar4;
__strong id ivar5;
}
@end

const uint8_t *class_getIvarLayout(Class cls) 函数获取到的是一个 uint8_t 的数组,一个 uint8_t 在 16 进制下是两位,所以编码的值每两位一对。这两位中,前面一位表示有多少个非 Strong 类型的成员变量,后面一位表示接下来有多少个 Strong 类型的成员变量,而这个顺序是根据成员变量声明的顺序决定的,此顺序也跟函数 Ivar *class_copyIvarList(Class cls, unsigned int *outCount) 获取到的一致。

在这个例子中,ClassA 类的 IvarLayout 的值为 0x211200

  • 0x211200 中的 21 中的 2 就代表一开始有两个非 Strong 类型的成员变量(ivar0 及 ivar1),其中的 1 则代表接下来有一个 Strong 类型的成员变量(ivar2);
  • 接下来的 12 中的 1 代表接下来有一个 非 Strong 类型的成员变量(ivar3),其中的 2 则代表接下来有两个 Strong 类型的成员变量(ivar4 及 ivar5);
  • 最后的 00 为结束符。

同理我们可以推测出 ClassA 类的 WeakIvarLayout 的值为:0x111100

本段参考资料:Objective-C Class Ivar Layout 探索

代码

基于上面介绍的几个函数,串连起来就可以实现获取一般对象的强引用对象的功能了,代码如下:

- (NSArray<NSValue *> *)strongIvarsWithObject:(id)object
{
NSMutableArray<NSValue *> *strongIvars = [NSMutableArray new];

__unsafe_unretained Class previousClass = nil;
__unsafe_unretained Class currentClass = object_getClass(object);

// 往上一直获取父类,直到 NSObject
while (previousClass != currentClass) {
// 获取当前类的 Strong 成员变量布局
const uint8_t * ivarLayout = class_getIvarLayout(currentClass);

if (ivarLayout) {
// 找出 Strong 成员变量的位置
NSMutableIndexSet *interestingIndexes = [NSMutableIndexSet new];
NSUInteger index = 0;
while ((*ivarLayout) != '\x00') {
// 获取高位(高位表示非 Strong 成员变量的个数)及低位(低位表示 Strong 成员变量的个数)
int upperNibble = (*ivarLayout & 0xf0) >> 4;
int lowerNibble = *ivarLayout & 0xf;

// 跳过非 Strong 成员变量的个数
index += upperNibble;

// 保存本次 Strong 成员变量的位置
[interestingIndexes addIndexesInRange:NSMakeRange(index, lowerNibble)];
index += lowerNibble;

++ivarLayout;
}

// 获取当前类的成员变量数组
unsigned int ivarCount;
Ivar *ivars = class_copyIvarList(currentClass, (&ivarCount));

// 遍历本类中的所有成员变量,保存位置在 interestingIndexes 中的成员变量
for (NSUInteger i = 0; i < ivarCount; ++i) {
if ([interestingIndexes containsIndex:i]) {
Ivar ivar = ivars[i];

// 此处为简单化,直接把 Ivar 数据结构 NSValue 化,也可以创建一个表示 Ivar 的类
NSValue *ivarValue = [NSValue value:&ivar withObjCType:@encode(Ivar)];
[strongIvars addObject:ivarValue];
}
}
}

previousClass = currentClass;
currentClass = class_getSuperclass(currentClass);
}

return [strongIvars copy];
}

我写的这个方法做了一些简化,完整的代码在 FBClassStrongLayout.m 中,感兴趣的可以看看。


下篇博客再介绍一下获取 Block 对象的强引用对象的原理。