FBRetainCycleDetector 源码笔记

FBRetainCycleDetector 是 Facebook 开源的用于检查循环引用的库。简单来说它的工作原理就是从传入的目标对象开始查找其强引用的对象列表,在这个强引用对象列表里继续查找强引用对象,默认查找 10 层。最后在整个有向图中应用 DFS 算法查找环。如果有向图存在环,则说明目标对象存在循环引用。
其中查找强引用对象主要涉及到 NSObject 和 NSBlock 的强引用列表,需要分析 NSObject、NSBlock 类的内存布局,这也是该框架的核心内容。除此之外还有 Struct、NSTimer、Associated 等细分场景。

用法示例

1
2
3
4
5
6
_RCDTestClass *testObject = [_RCDTestClass new];
testObject.object = testObject;

FBRetainCycleDetector *detector = [FBRetainCycleDetector new];
[detector addCandidate:testObject];
NSSet *retainCycles = [detector findRetainCycles];

创建 FBRetainCycleDetector 对象,将需要检查的目标对象通过 addCandidate 方法添加。然后调用 findRetainCycles 方法即可获得循环引用结果:

1
2
3
4
5
{(
(
"-> _object -> _RCDTestClass "
)
)}

分层概览

以下是 FBRetainCycleDetector 源码的文件夹分层概览。大体介绍下各部分职责,从上往下逐渐底层。

1、Associations 文件夹:通过 fishhook hook objc_setAssociatedObjectobjc_removeAssociatedObjects 方法,在 hook 方法里根据 OBJC_ASSOCIATION_RETAINOBJC_ASSOCIATION_RETAIN_NONATOMIC 判断拿到强引用对象。(其中 fishhook 的分析可见上一篇博客:fishhook 源码笔记

2、Detector 文件夹:FBNodeEnumerator 继承自 NSEnumerator 实现链表结构。FBRetainCycleDetector 根据传入的目标对象开始查找其强引用的对象列表,在这个强引用对象列表里继续查找强引用对象,默认查找 10 层。最后在整个有向图中应用 DFS 算法查找环。如果有向图存在环,则说明目标对象存在循环引用。

3、Filtering 文件夹:提供默认的过滤逻辑。

4、Graph 文件夹:获取 NSObject 和 NSBlock 对象的强引用对象列表。其中对于 NSTimer,从 Context 中获取强引用对象。

5、Layout 文件夹:根据 Block 结构体获取 NSBlock 的强引用布局。根据 IvarLayout 获取 NSObject 的强引用布局。将 Ivar 封装成 FBIvarReference 对象进行处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
├── FBRetainCycleDetector
│   ├── Associations
│   │   ├── FBAssociationManager.h
│   │   ├── FBAssociationManager.mm
│   ├── Detector
│   │   ├── FBNodeEnumerator.h
│   │   ├── FBNodeEnumerator.mm
│   │   ├── FBRetainCycleDetector.h
│   │   └── FBRetainCycleDetector.mm
│   ├── Filtering
│   │   ├── FBStandardGraphEdgeFilters.h
│   │   └── FBStandardGraphEdgeFilters.mm
│   ├── Graph
│   │   ├── FBObjectGraphConfiguration.h
│   │   ├── FBObjectGraphConfiguration.m
│   │   ├── FBObjectiveCBlock.h
│   │   ├── FBObjectiveCBlock.m
│   │   ├── FBObjectiveCGraphElement.h
│   │   ├── FBObjectiveCGraphElement.mm
│   │   ├── FBObjectiveCObject.h
│   │   ├── FBObjectiveCObject.m
│   │   └── Specialization
│   │   ├── FBObjectiveCNSCFTimer.h
│   │   └── FBObjectiveCNSCFTimer.mm
│   └── Layout
│   ├── Blocks
│   │   ├── FBBlockInterface.h
│   │   ├── FBBlockStrongLayout.h
│   │   ├── FBBlockStrongLayout.m
│   │   ├── FBBlockStrongRelationDetector.h
│   │   └── FBBlockStrongRelationDetector.m
│   └── Classes
│   ├── FBClassStrongLayout.h
│   ├── FBClassStrongLayout.mm
│   ├── FBClassStrongLayoutHelpers.h
│   ├── FBClassStrongLayoutHelpers.m
│   ├── Parser
│   │   ├── BaseType.h
│   │   ├── FBStructEncodingParser.h
│   │   ├── FBStructEncodingParser.mm
│   │   ├── Struct.h
│   │   ├── Struct.mm
│   │   └── Type.h
│   └── Reference
│   ├── FBIvarReference.h
│   ├── FBIvarReference.m
│   ├── FBObjectInStructReference.h
│   ├── FBObjectInStructReference.m
│   └── FBObjectReference.h

源码分析

以下来根据源码具体分析下 FBRetainCycleDetector 是怎样获取 NSObject、NSBlock 的强引用对象的。从下层开始往上分析。

Layout

Classes

1、FBIvarReference

介绍几个关键方法:

1
2
3
4
- (id)objectReferenceFromObject:(id)object
{
return object_getIvar(object, _ivar);
}

通过 object_getIvar 方法,获取 _ivar 指向的对象。在收集强引用对象列表时,遍历对象 ivar 列表,用这个方法来判断属性是否有值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- (FBType)_convertEncodingToType:(const char *)typeEncoding
{
if (typeEncoding == NULL) {
return FBUnknownType;
}

if (typeEncoding[0] == '{') {
return FBStructType;
}

if (typeEncoding[0] == '@') {
// It's an object or block

// Let's try to determine if it's a block. Blocks tend to have
// @? typeEncoding. Docs state that it's undefined type, so
// we should still verify that ivar with that type is a block
if (strncmp(typeEncoding, "@?", 2) == 0) {
return FBBlockType;
}

return FBObjectType;
}

return FBUnknownType;
}

根据 typeEncoding 判断对象类型,是 Struct、NSBlock 或者普通的 NSObject 类型。typeEncoding 字符具体的对应关系可参考 Apple 文档:Type Encodings

1
2
3
4
5
6
7
8
9
10
11
12
- (instancetype)initWithIvar:(Ivar)ivar
{
if (self = [super init]) {
_name = @(ivar_getName(ivar));
_type = [self _convertEncodingToType:ivar_getTypeEncoding(ivar)];
_offset = ivar_getOffset(ivar);
_index = _offset / sizeof(void *);
_ivar = ivar;
}

return self;
}

接收 Ivar 参数的 init 方法。
根据 ivar_getName 获取 ivar name 字符串。
根据 ivar_getTypeEncoding 获取 typeEncoding。
根据 ivar_getOffset 获取 ivar 的偏移量。
有了 _offset,除去指针类型的大小,即可获得 ivar 的角标 index。

2、FBClassStrongLayout

1、FBGetClassReferences 类方法:该类方法用于收集目标类的所有 ivar,并封装成 FBObjectInStructReference 数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
NSArray<id<FBObjectReference>> *FBGetClassReferences(Class aCls) {
NSMutableArray<id<FBObjectReference>> *result = [NSMutableArray new];

unsigned int count;
// 获取目标类的所有 ivar
Ivar *ivars = class_copyIvarList(aCls, &count);

for (unsigned int i = 0; i < count; ++i) {
Ivar ivar = ivars[i];
// 将 ivar 封装成 FBIvarReference
FBIvarReference *wrapper = [[FBIvarReference alloc] initWithIvar:ivar];

// Struct 类型特殊处理
if (wrapper.type == FBStructType) {
std::string encoding = std::string(ivar_getTypeEncoding(wrapper.ivar));
NSArray<FBObjectInStructReference *> *references = FBGetReferencesForObjectsInStructEncoding(wrapper, encoding);

[result addObjectsFromArray:references];
} else {
[result addObject:wrapper];
}
}
free(ivars);

return [result copy];
}

2、FBGetMinimumIvarIndex 类方法:该类方法用于获取目标类的 minimum Ivar index,即第一个 ivar 的角标,用于判断强引用 ivar 角标位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static NSUInteger FBGetMinimumIvarIndex(__unsafe_unretained Class aCls) {
NSUInteger minimumIndex = 1;
unsigned int count;
Ivar *ivars = class_copyIvarList(aCls, &count);

if (count > 0) {
// 获取类的第一个 ivar
Ivar ivar = ivars[0];
// 根据 ivar_getOffset 方法获取该 ivar 的偏移
ptrdiff_t offset = ivar_getOffset(ivar);
// 偏移除去指针大小即可获得第一个 ivar 的角标 index
minimumIndex = offset / (sizeof(void *));
}

free(ivars);

return minimumIndex;
}

3、FBGetLayoutAsIndexesForDescription 类方法:该类方法用于收集给定 Ivar layout 里的强引用角标 Range 集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static NSIndexSet *FBGetLayoutAsIndexesForDescription(NSUInteger minimumIndex, const uint8_t *layoutDescription) {
NSMutableIndexSet *interestingIndexes = [NSMutableIndexSet new];
// 从初始 index 开始计算
NSUInteger currentIndex = minimumIndex;

while (*layoutDescription != '\x00') {
// 取高 4 位:表示非 `__strong` 所有权的实例变量数量
int upperNibble = (*layoutDescription & 0xf0) >> 4;
// 取低 4 位:表示 `__strong` 所有权的实例变量数量
int lowerNibble = *layoutDescription & 0xf;

// 跳过高 4 位,即非 `__strong` 变量
// Upper nimble is for skipping
currentIndex += upperNibble;

// 将低 4 位变量 Range 加到集合里
// Lower nimble describes count
[interestingIndexes addIndexesInRange:NSMakeRange(currentIndex, lowerNibble)];
// 跳过低 4 位,从下一 uint8_t 再次查找
currentIndex += lowerNibble;

// 取出下一 uint8_t 进行下一循环
++layoutDescription;
}

这个方法属于查找 NSObject 类型对象的强引用对象列表的关键方法。

FBRetainCycleDetectorTests.mm 单测里提供的 _RCDTestClass 类为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef void (^_RCDTestBlockType)();

typedef struct {
id<NSObject> model;
__weak id<NSObject> weakModel;
} _RCDTestStruct;

@interface _RCDTestClass : NSObject
@property (nonatomic, strong) NSObject *object;
@property (nonatomic, strong) NSObject *secondObject;
@property (nonatomic, copy) NSArray *array;
@property (nonatomic, weak) NSObject *weakObject;
@property (nonatomic, strong) _RCDTestBlockType block;
@property (nonatomic, assign) _RCDTestStruct someStruct;
@end
@implementation _RCDTestClass
@end

通过 class_getIvarLayout 方法获取的 Ivar layout,点进 class_getIvarLayout 方法能看到其返回值类型为 uint8_t 的指针:

1
2
3
4
5
6
7
8
9
10
/** 
* Returns a description of the \c Ivar layout for a given class.
*
* @param cls The class to inspect.
*
* @return A description of the \c Ivar layout for \e cls.
*/
OBJC_EXPORT const uint8_t * _Nullable
class_getIvarLayout(Class _Nullable cls)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

** class_getIvarLayout 方法返回值是指向 uint8_t 的指针。uint8_t 大小为一个字节,即 8 位,高 4 位表示非 __strong 所有权的实例变量数量,低 4 位表示 __strong 所有权的实例变量数量。**

fullLayout

从上图可看出 _RCDTestClass 类的 Ivar layout 为:\x03\x12,即两组 uint8_t
第一段的 \x03 代表 0 个非 __strong 的实例变量,3 个 __strong 的实例变量
第一段的 \x12 代表 1 个非 __strong 的实例变量,2 个 __strong 的实例变量

注意:_RCDTestClass 类的 someStruct 为 struct,比较特殊。即使该属性声明为 assign,但其 model 为 id 类型的强引用,所以 class_getIvarLayout 方法的返回为 \x03\x12

getLayout

具体的取值逻辑见上图,即:
第一次循环 *layoutDescription\x03,与操作 0xf0 之后右移 4 位获得高 4 位:0;与操作 0xf 之后获得低 4 位:3
第二次循环 *layoutDescription ++ 之后 为 \x12,与操作 0xf0 之后右移 4 位获得高 4 位:1;与操作 0xf 之后获得低 4 位:2

最后集合结果为:

1
<NSMutableIndexSet: 0x7fb65a71c6c0>[number of indexes: 5 (in 2 ranges), indexes: (1-3 5-6)]

4、FBGetStrongReferencesForClass 类方法:该类方法用于收集目标类的强引用 ivar 封装成的 FBIvarReference 数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
static NSArray<id<FBObjectReference>> *FBGetStrongReferencesForClass(Class aCls) {
NSArray<id<FBObjectReference>> *ivars = [FBGetClassReferences(aCls) filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
if ([evaluatedObject isKindOfClass:[FBIvarReference class]]) {
FBIvarReference *wrapper = evaluatedObject;
// 过滤未知类型
return wrapper.type != FBUnknownType;
}
return YES;
}]];

// 获取目标类的 Ivar layout
const uint8_t *fullLayout = class_getIvarLayout(aCls);

if (!fullLayout) {
return @[];
}

// 获取 minimum Ivar index,即第一个 ivar 的角标
NSUInteger minimumIndex = FBGetMinimumIvarIndex(aCls);
// 根据 Ivar layout 收集到的强引用角标 Range 集合
NSIndexSet *parsedLayout = FBGetLayoutAsIndexesForDescription(minimumIndex, fullLayout);

NSArray<id<FBObjectReference>> *filteredIvars =
[ivars filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id<FBObjectReference> evaluatedObject,
NSDictionary *bindings) {
// 遍历 ivar 数组,根据收集到的强引用角标 Range 集合筛选出强引用类型
return [parsedLayout containsIndex:[evaluatedObject indexInIvarLayout]];
}]];

return filteredIvars;
}

5、FBGetObjectStrongReferences 对象方法:该方法用于收集目标对象的类的强引用 ivar 封装成的 FBIvarReference 数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
NSArray<id<FBObjectReference>> *FBGetObjectStrongReferences(id obj,
NSMutableDictionary<Class, NSArray<id<FBObjectReference>> *> *layoutCache) {
NSMutableArray<id<FBObjectReference>> *array = [NSMutableArray new];

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

// 同时循环向上寻找父类,直到根类
while (previousClass != currentClass) {
NSArray<id<FBObjectReference>> *ivars;

// 缓存节省查找次数
if (layoutCache && currentClass) {
ivars = layoutCache[currentClass];
}

if (!ivars) {
ivars = FBGetStrongReferencesForClass(currentClass);
if (layoutCache && currentClass) {
layoutCache[(id<NSCopying>)currentClass] = ivars;
}
}
[array addObjectsFromArray:ivars];

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

return [array copy];
}

Blocks

1、FBBlockInterface

根据 Clang 文档 定义的 Block 结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
enum { // Flags from BlockLiteral
BLOCK_HAS_COPY_DISPOSE = (1 << 25),
BLOCK_HAS_CTOR = (1 << 26), // helpers have C++ code
BLOCK_IS_GLOBAL = (1 << 28),
BLOCK_HAS_STRET = (1 << 29), // IFF BLOCK_HAS_SIGNATURE
BLOCK_HAS_SIGNATURE = (1 << 30),
};

struct BlockDescriptor {
unsigned long int reserved; // NULL
unsigned long int size;
// optional helper functions
void (*copy_helper)(void *dst, void *src); // IFF (1<<25)
void (*dispose_helper)(void *src); // IFF (1<<25)
const char *signature; // IFF (1<<30)
};

struct BlockLiteral {
void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
int flags;
int reserved;
void (*invoke)(void *, ...);
struct BlockDescriptor *descriptor;
// imported variables
};

其中,flags 值为 BLOCK_HAS_CTOR 时,表示存在 c++ 函数,可能指针不对齐,过滤此类场景;值为 BLOCK_HAS_STRET 时,表示存在 dispose 函数,能够释放持有的对象。

其中,dispose_helper 函数即为 Block 释放其强持有对象的函数,通过该函数可模拟释放过程,辨别 Block 的哪些变量为强引用对象。

2、FBBlockStrongRelationDetector

该类的作用是用于找出 Block 持有的强引用对象。根据 Block 持有的对象一一包装成 FBBlockStrongRelationDetector 对象,通过调用 dispose_helper 函数模拟释放强引用对象过程辨别 Block 的哪些变量为强引用对象。

注意:该类因重写了 release 方法,需要标记为非 ARC 编译。podspec 声明及文件宏定义校验。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#if __has_feature(objc_arc)
#error This file must be compiled with MRR. Use -fno-objc-arc flag.
#endif

@implementation FBBlockStrongRelationDetector

- (oneway void)release
{
_strong = YES;
}

......

- (oneway void)trueRelease
{
[super release];
}

......

@end

如代码示例,重写 release 函数,将成员变量 _strong 标记为 YES,代表这是个强引用对象。另外 trueRelease 方法,执行真正的 release 操作。

3、FBBlockStrongLayout

1、_GetBlockStrongLayout 方法:该方法用于获取 Block 强持有对象的角标列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
static NSIndexSet *_GetBlockStrongLayout(void *block) {
struct BlockLiteral *blockLiteral = block;

/**
BLOCK_HAS_CTOR - Block has a C++ constructor/destructor, which gives us a good chance it retains
objects that are not pointer aligned, so omit them.

!BLOCK_HAS_COPY_DISPOSE - Block doesn't have a dispose function, so it does not retain objects and
we are not able to blackbox it.
*/
// 1、flags 值为 BLOCK_HAS_CTOR 表示 block 存在 c++ 构造器/析构器,可能存在指针不对齐,过滤此类场景
// 2、flags 值为 BLOCK_HAS_COPY_DISPOSE 表示 block 存在 dispose 函数,可用来模拟释放过程,辨别强引用对象
if ((blockLiteral->flags & BLOCK_HAS_CTOR)
|| !(blockLiteral->flags & BLOCK_HAS_COPY_DISPOSE)) {
return nil;
}

// 取出 dispose 函数,用来模拟释放过程,辨别强引用对象
void (*dispose_helper)(void *src) = blockLiteral->descriptor->dispose_helper;
const size_t ptrSize = sizeof(void *);

// 获取 block 持有的指针的数量
// Figure out the number of pointers it takes to fill out the object, rounding up.
const size_t elements = (blockLiteral->descriptor->size + ptrSize - 1) / ptrSize;

// Create a fake object of the appropriate length.
void *obj[elements];
void *detectors[elements];

// 根据持有的指针的数量创建 FBBlockStrongRelationDetector 对象
for (size_t i = 0; i < elements; ++i) {
FBBlockStrongRelationDetector *detector = [FBBlockStrongRelationDetector new];
obj[i] = detectors[i] = detector;
}

@autoreleasepool {
// 调用 block 的 dispose 函数,传入我们自己创建的模拟对象 FBBlockStrongRelationDetector 数组
// 如果目标位置是 block 强持有的对象,那么调用 dispose_helper 函数,该对象会被释放,即执行 release 函数
// 因为 FBBlockStrongRelationDetector 重写了 release 函数,该函数执行时会将自己 isStrong 标记为 YES
dispose_helper(obj);
}

// Run through the release detectors and add each one that got released to the object's
// strong ivar layout.
NSMutableIndexSet *layout = [NSMutableIndexSet indexSet];

// 再次循环 elements 个数,判断 FBBlockStrongRelationDetector 字段 isStrong 为 YES 的说明这个位置的对象被 block 强持有
// 将这个位置 index 记录下来
for (size_t i = 0; i < elements; ++i) {
FBBlockStrongRelationDetector *detector = (FBBlockStrongRelationDetector *)(detectors[i]);
if (detector.isStrong) {
[layout addIndex:i];
}

// detector 对象执行真正的 release 函数,释放对象
// Destroy detectors
[detector trueRelease];
}

return layout;
}

2、FBGetBlockStrongReferences 方法:根据 _GetBlockStrongLayout 方法拿到的 block 强持有的角标获,取该角标位置处的对象,如果该位置存在值的话说明有强持有的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
NSArray *FBGetBlockStrongReferences(void *block) {
if (!FBObjectIsBlock(block)) {
return nil;
}

NSMutableArray *results = [NSMutableArray new];

void **blockReference = block;
NSIndexSet *strongLayout = _GetBlockStrongLayout(block);
[strongLayout enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
// 根据 _GetBlockStrongLayout 方法拿到的 block 强持有的角标获,取该角标位置处的对象
void **reference = &blockReference[idx];

// 是否存在对象或者指向对象的指针
if (reference && (*reference)) {
id object = (id)(*reference);

// 如果该位置存在值的话说明有强持有的对象
if (object) {
[results addObject:object];
}
}
}];

return [results autorelease];
}

Graph

有了上述章节提供的工具方法,即可查找对象的强引用列表了。主要是查找 NSObject 对象的 FBObjectiveCObject 及查找 NSBlock 对象的 FBObjectiveCBlock

1、FBObjectiveCObject

收集 NSObject 类别对象的强引用列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
- (NSSet *)allRetainedObjects
{
Class aCls = object_getClass(self.object);
if (!self.object || !aCls) {
return nil;
}

// 获取强引用 ivar 列表
NSArray *strongIvars = FBGetObjectStrongReferences(self.object, self.configuration.layoutCache);

// 获取父类收集的强引用列表(FBAssociationManager 处理)
NSMutableArray *retainedObjects = [[[super allRetainedObjects] allObjects] mutableCopy];

for (id<FBObjectReference> ref in strongIvars) {
id referencedObject = [ref objectReferenceFromObject:self.object];

// 如果存在值的话说明存在持有的强引用对象,将其封装成 FBObjectiveCGraphElement 对象收集起来
if (referencedObject) {
NSArray<NSString *> *namePath = [ref namePath];
FBObjectiveCGraphElement *element = FBWrapObjectGraphElementWithContext(self,
referencedObject,
self.configuration,
namePath);
if (element) {
[retainedObjects addObject:element];
}
}
}

// 一下为一些过滤操作,以及对可变类型的循环检查等操作

if ([NSStringFromClass(aCls) hasPrefix:@"__NSCF"]) {
......
}

2、FBObjectiveCNSCFTimer

其中,对于 NSTimer 类型需要额外做些处理。通过 CFRunLoopTimerGetContext 方法获取 NSTimer 对象的 context,将其 info 强转成我们自定义的 _FBNSCFTimerInfoStruct 结构体。从中获取 targetuserInfo 信息,判断是否有值,有值说明存在强引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
typedef struct {
long _unknown; // This is always 1
id target;
SEL selector;
NSDictionary *userInfo;
} _FBNSCFTimerInfoStruct;

- (NSSet *)allRetainedObjects
{
// Let's retain our timer
__attribute__((objc_precise_lifetime)) NSTimer *timer = self.object;

if (!timer) {
return nil;
}

// 获取父类收集的强引用列表(FBAssociationManager 处理)
NSMutableSet *retained = [[super allRetainedObjects] mutableCopy];

CFRunLoopTimerContext context;
// 获取 NSTimer 对象的 context
CFRunLoopTimerGetContext((CFRunLoopTimerRef)timer, &context);

// If it has a retain function, let's assume it retains strongly
if (context.info && context.retain) {
// 将 info 强转成我们自定义的 _FBNSCFTimerInfoStruct 结构体
_FBNSCFTimerInfoStruct infoStruct = *(_FBNSCFTimerInfoStruct *)(context.info);
if (infoStruct.target) {
FBObjectiveCGraphElement *element = FBWrapObjectGraphElementWithContext(self, infoStruct.target, self.configuration, @[@"target"]);
// 如果 target 存在值的话说明存在持有的强引用对象,将其封装成 FBObjectiveCGraphElement 对象收集起来
if (element) {
[retained addObject:element];
}
}
if (infoStruct.userInfo) {
FBObjectiveCGraphElement *element = FBWrapObjectGraphElementWithContext(self, infoStruct.userInfo, self.configuration, @[@"userInfo"]);
// 如果 userInfo 存在值的话说明存在持有的强引用对象,将其封装成 FBObjectiveCGraphElement 对象收集起来
if (element) {
[retained addObject:element];
}
}
}

return retained;
}

3、FBObjectiveCBlock

收集 NSBlock 类别对象的强引用列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (NSSet *)allRetainedObjects
{
// 获取父类收集的强引用列表(FBAssociationManager 处理)
NSMutableArray *results = [[[super allRetainedObjects] allObjects] mutableCopy];

// Grab a strong reference to the object, otherwise it can crash while doing
// nasty stuff on deallocation
__attribute__((objc_precise_lifetime)) id anObject = self.object;

void *blockObjectReference = (__bridge void *)anObject;
// 获取强引用列表
NSArray *allRetainedReferences = FBGetBlockStrongReferences(blockObjectReference);

for (id object in allRetainedReferences) {
FBObjectiveCGraphElement *element = FBWrapObjectGraphElement(self, object, self.configuration);
// 如果存在值的话说明存在持有的强引用对象,将其封装成的 FBObjectiveCGraphElement 对象收集起来
if (element) {
[results addObject:element];
}
}

return [NSSet setWithArray:results];
}

Associations

FBObjectiveCBlockFBObjectiveCObject 都继承自 FBObjectiveCGraphElement,包括 FBObjectiveCNSCFTimer 都通过父类方法从 FBAssociationManager 收集强引用对象。

FBAssociationManager 原理即通过 fishhook 工具 hook objc_setAssociatedObjectobjc_removeAssociatedObjects 方法,在 hook 方法里根据 OBJC_ASSOCIATION_RETAINOBJC_ASSOCIATION_RETAIN_NONATOMIC 判断拿到强引用对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void fb_objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy) {
{
std::lock_guard<std::mutex> l(*_associationMutex);
// Track strong references only
if (policy == OBJC_ASSOCIATION_RETAIN ||
policy == OBJC_ASSOCIATION_RETAIN_NONATOMIC) {
_threadUnsafeSetStrongAssociation(object, key, value);
} else {
// We can change the policy, we need to clear out the key
_threadUnsafeResetAssociationAtKey(object, key);
}
}

fb_orig_objc_setAssociatedObject(object, key, value, policy);
}

其实现思路和系统的 Associated 一致:维护一个全局的 Hash 表,接管系统的处理逻辑
objc_setAssociatedObject 方法将 object 做为 key 存进全局的 Hash 表
objc_getAssociatedObject 方法将 object 做为 key 从进全局的 Hash 表取出数据
objc_removeAssociatedObjects 方法将 object 做为 key 从进全局的 Hash 表删除数据

1
2
3
4
5
6
7
8
9
10
11
12
OBJC_EXPORT void
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
id _Nullable value, objc_AssociationPolicy policy)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);

OBJC_EXPORT id _Nullable
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);

OBJC_EXPORT void
objc_removeAssociatedObjects(id _Nonnull object)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);

Detector

FBRetainCycleDetectorfindRetainCycles 方法根据之前 addCandidate 方法传入的目标对象开始查找其强引用的对象列表,在这个强引用对象列表里继续查找强引用对象,默认查找 10 层。最后在整个有向图中应用 DFS 算法查找环。如果有向图存在环,则说明目标对象存在循环引用。

查找有向图中是否存在环的 DFS 算法并不在本篇文章探究范围内,所以文章就到这吧。

至此,FBRetainCycleDetector 的源码笔记就结束了。

Reference