概述
FBRetainCycleDetector
是facebook开源的一个用来检测对象是否有强引用循环的静态库。
strong和weak
strong
和weak
在声明中使用表示这是一个强引用还是弱引用对象。
- 强引用:只要引用存在,对象就不能被销毁。
- 弱引用:弱引用不会导致对象不能销毁,只要没有强引用了,对象就会销毁,对象销毁后,弱引用会自动设置为nil。
- 当一个对象不再有strong类型的指针指向它的时候,它就会被释放,即使该对象还有weak类型的指针指向它。
- 一旦最后一个指向该对象的strong类型的指针离开,这个对象将被释放,如果这个时候还有weak指针指向该对象,则会清除所有剩余的weak指针。
在OC中strong就相当于retain属性,而weak相当于assign。使用weak也就是为了避免retain cycles,比如父类中含有子类对象(retain了子类),子类中又有父类的对象(子类又retain了父类),这样就会形成retain cycles,导致两个对象都无法release。而FBRetainCycleDetector做的事情就是去检测是否存在这样的retain cycles。下面是循环引用的一个例图:
在 Objective-C 中找循环引用类似于在一个有向无环图(directed acyclic graph)中找环, 而节点就是对象,边就是对象之间的引用(如果对象A持有对象B,那么,A到B之间就存在着引用)。我们的 Objective-C 对象已经在我们的图中,我们要做的就是用深度优先搜索遍历它。
使用方法
构建一个引用循环,然后使用FBRetainCycleDetector提供的接口来进行检测,具体代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
MyObject1* obj1 = [MyObject1 new];
MyObject2* obj2 = [MyObject2 new];
MyObject3* obj3 = [MyObject3 new];
obj1.object1 = obj2;
obj1.name =
@"obj1";
obj2.object2 = obj3;
obj2.name =
@"obj2";
obj3.object3 = obj1;
obj3.name =
@"obj3";
FBRetainCycleDetector *detector = [FBRetainCycleDetector new];
[detector addCandidate:obj1];
NSSet<
NSArray<FBObjectiveCGraphElement *> *> *retainCycles = [detector findRetainCycles];
NSLog(
@"%@", retainCycles);
|
运行结果如下:
1
2
3
4
5
6
7
|
2016-05-15 12:38:25.208 FBRetainCycleDetectorDemo[57524:6267406] {(
(
"-> _object3 -> MyObject1 ",
"-> _object1 -> MyObject2 ",
"-> _object2 -> MyObject3 "
)
)}
|
也就是说检测的对象存在引用循环。
执行流程
首先需要初始化一个FBRetainCycleDetector检测器,先来分析这个类都有什么接口及其功能。
1
|
FBRetainCycleDetector *detector = [FBRetainCycleDetector new];
|
new 相当于[[alloc] init]
,所以会调用FBRetainCycleDetector的init函数。
1
2
3
4
5
6
7
|
- (
instancetype)init
{
return [
self initWithConfiguration:
[[FBObjectGraphConfiguration alloc] initWithFilterBlocks:FBGetStandardGraphEdgeFilters()
shouldInspectTimers:
YES]];
}
|
调用了initWithConfiguration:
初始化了一个标准的过滤器,这个过滤器是为了过滤引用循环中的一些类和方法。
然后调用addCandidate
添加一个需要被检测的对象。
1
2
3
4
5
6
7
|
- (
void)addCandidate:(
id)candidate
{
FBObjectiveCGraphElement *graphElement = FBWrapObjectGraphElement(candidate, _configuration);
[_candidates addObject:graphElement];
}
|
跟进FBWrapObjectGraphElement函数,发现里面调用了-[FBObjectiveCGraphElement initWithObject:configuration:namePath:]
方法,其中就是初始化一个FBObjectiveCGraphElement。
重点是findRetainCycles
这个方法来查找是否存在引用循环。这个方法调用了findRetainCyclesWithMaxCycleLength:
来根据指定的深度进行深度搜索,默认深度是10。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
- (
NSSet<
NSArray<FBObjectiveCGraphElement *> *> *)findRetainCyclesWithMaxCycleLength:(
NSUInteger)length
{
NSMutableSet<
NSArray<FBObjectiveCGraphElement *> *> *allRetainCycles = [
NSMutableSet new];
for (FBObjectiveCGraphElement *graphElement
in _candidates) {
NSSet<
NSArray<FBObjectiveCGraphElement *> *> *retainCycles = [
self _findRetainCyclesInObject:graphElement
stackDepth:length];
[allRetainCycles unionSet:retainCycles];
}
[_candidates removeAllObjects];
return allRetainCycles;
}
|
继续跟进_findRetainCyclesInObject:stackDepth:
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
- (
NSSet<
NSArray<FBObjectiveCGraphElement *> *> *)_findRetainCyclesInObject:(FBObjectiveCGraphElement *)graphElement
stackDepth:(
NSUInteger)stackDepth
{
NSMutableSet<
NSArray<FBObjectiveCGraphElement *> *> *retainCycles = [
NSMutableSet new];
FBNodeEnumerator *wrappedObject = [[FBNodeEnumerator alloc] initWithObject:graphElement];
NSMutableArray<FBNodeEnumerator *> *stack = [
NSMutableArray new];
NSMutableSet<FBNodeEnumerator *> *objectsOnPath = [
NSMutableSet new];
[stack addObject:wrappedObject];
while ([stack count] >
0) {
@autoreleasepool {
FBNodeEnumerator *top = [stack lastObject];
[objectsOnPath addObject:top];
FBNodeEnumerator *firstAdjacent = [top nextObject];
if (firstAdjacent) {
BOOL shouldPushToStack =
NO;
if ([objectsOnPath containsObject:firstAdjacent]) {
NSUInteger index = [stack indexOfObject:firstAdjacent];
NSInteger length = [stack count] - index;
if (index ==
NSNotFound) {
shouldPushToStack =
YES;
}
else {
NSRange cycleRange =
NSMakeRange(index, length);
NSMutableArray<FBNodeEnumerator *> *cycle = [[stack subarrayWithRange:cycleRange] mutableCopy];
[cycle replaceObjectAtIndex:
0 withObject:firstAdjacent];
[retainCycles addObject:[
self _shiftToUnifiedCycle:[
self _unwrapCycle:cycle]]];
}
}
else {
shouldPushToStack =
YES;
}
if (shouldPushToStack) {
if ([stack count] < stackDepth) {
[stack addObject:firstAdjacent];
}
}
}
else {
[stack removeLastObject];
[objectsOnPath removeObject:top];
}
}
}
return retainCycles;
}
|
上面这个函数就是DFS深度搜索的代码,以搜索对象相关的所有属性构成的搜索树进行深度搜索,一旦发现构成了环就记录下,然后继续搜索直接到达到指定的深度或搜索完毕。以上面的例子为例,搜索图是这样的:
图中从obj1节点开始搜索,依次遍历,n1、obj2、n2、obj3、n3、obj1,然后发现构成了环,然后保存环节点。
获取强引用
怎样获取自己所持有的所有引用的对象,FBRetainCycleDetector把传入的对象都封装成了一个FBObjectiveCGraphElement对象,根据对象的不同类型分为派生出子类FBObjectiveCBlock(Block)对象,FBObjectiveCBlock对象以及FBObjectiveCNSCFTimer(NSTimer)对象,不同对象获取所持有的引用时都会调用父类的allRetainedObjects,然后再进行自己的处理。
先来看看FBObjectiveCGraphElement的allRetainedObjects方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
- (
NSSet *)allRetainedObjects
{
NSArray *retainedObjectsNotWrapped = [FBAssociationManager associationsForObject:_object];
NSMutableSet *retainedObjects = [
NSMutableSet new];
for (
id obj
in retainedObjectsNotWrapped) {
[retainedObjects addObject:FBWrapObjectGraphElementWithContext(obj,
_configuration,
@[
@"__associated_object"])];
}
return retainedObjects;
}
|
[FBAssociationManager associationsForObject:]
获取该对象所有通过objc_setAssociatedObject
关联的对象。因为后者会增加对象的引用,为了做到这一点,必须main.m
中调用[FBAssociationManager hook]
进行fishhook。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
+ (
void)hook
{
#if _INTERNAL_RCD_ENABLED
std::lock_guard<
std::mutex> l(*FB::AssociationManager::hookMutex);
rcd_rebind_symbols((
struct rcd_rebinding[
2]){
{
"objc_setAssociatedObject",
(
void *)FB::AssociationManager::fb_objc_setAssociatedObject,
(
void **)&FB::AssociationManager::fb_orig_objc_setAssociatedObject
},
{
"objc_removeAssociatedObjects",
(
void *)FB::AssociationManager::fb_objc_removeAssociatedObjects,
(
void **)&FB::AssociationManager::fb_orig_objc_removeAssociatedObjects
}},
2);
FB::AssociationManager::hookTaken =
true;
#endif
}
|
该函数hook了objc_setAssociatedObject
和objc_removeAssociatedObjects
这两个C函数来监控。接下来看看子类的不同处理。
FBObjectiveCObject
FBObjectiveCObject的allRetainedObjects
方法中首先调用了_unfilteredRetainedObjects
获取所有引用对象,然后调用了filterObjects:
调用过滤接口来进行过滤。
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
- (
NSArray *)_unfilteredRetainedObjects
{
Class aCls = object_getClass(
self.object);
if (!
self.object || !aCls) {
return
nil;
}
NSArray *strongIvars = FBGetObjectStrongReferences(
self.object);
NSMutableArray *retainedObjects = [[[
super allRetainedObjects] allObjects] mutableCopy];
for (
id<FBObjectReference> ref
in strongIvars) {
id referencedObject = [ref objectReferenceFromObject:
self.object];
if (referencedObject) {
NSArray<
NSString *> *namePath = [ref namePath];
[retainedObjects addObject:FBWrapObjectGraphElementWithContext(referencedObject,
self.configuration,
namePath)];
}
}
if ([
NSStringFromClass(aCls) hasPrefix:
@"__NSCF"]) {
If we are dealing with toll-free bridged collections, we are not guaranteed that the collection
will hold only Objective-C objects. We are not able to check in runtime what callbacks it uses to
retain/release (if any) and we could easily crash here.
*/
return retainedObjects;
}
if (class_isMetaClass(aCls)) {
return
nil;
}
if ([aCls conformsToProtocol:
@protocol(NSFastEnumeration)]) {
BOOL retainsKeys = [
self _objectRetainsEnumerableKeys];
BOOL retainsValues = [
self _objectRetainsEnumerableValues];
BOOL isKeyValued =
NO;
if ([aCls instancesRespondToSelector:
@selector(objectForKey:)]) {
isKeyValued =
YES;
}
This codepath is prone to errors. When you enumerate a collection that can be mutated while enumeration
we fall into risk of crash. To save ourselves from that we will catch such exception and try again.
We should not try this endlessly, so at some point we will simply give up.
*/
NSInteger tries =
10;
for (
NSInteger i =
0; i < tries; ++i) {
NSMutableSet *temporaryRetainedObjects = [
NSMutableSet new];
@try {
for (
id subobject
in
self.object) {
if (retainsKeys) {
[temporaryRetainedObjects addObject:FBWrapObjectGraphElement(subobject,
self.configuration)];
}
if (isKeyValued && retainsValues) {
[temporaryRetainedObjects addObject:FBWrapObjectGraphElement([
self.object objectForKey:subobject],
self.configuration)];
}
}
}
@catch (
NSException *exception) {
continue;
}
[retainedObjects addObjectsFromArray:[temporaryRetainedObjects allObjects]];
break;
}
}
return retainedObjects;
}
|
该函数首先调用父类方法allRetainedObjects
获取associated objects。然后调用FBGetObjectStrongReferences
获取强引用的属性,并得到属性的对象,最后判断是否为集合来获取集合里面的引用。FBGetObjectStrongReferences内部调用了FBGetStrongReferencesForClass
通过类来获取强引用,后者先通过FBGetClassReferences
获取所有引用。
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
|
NSArray<
id<FBObjectReference>> *FBGetClassReferences(Class aCls) {
NSMutableArray<
id<FBObjectReference>> *result = [
NSMutableArray new];
unsigned
int count;
Ivar *ivars = class_copyIvarList(aCls, &count);
for (
unsigned
int i =
0; i < count; ++i) {
Ivar ivar = ivars[i];
FBIvarReference *wrapper = [[FBIvarReference alloc] initWithIvar:ivar];
if (wrapper.type == FBStructType) {
NSString *encoding = @(ivar_getTypeEncoding(wrapper.ivar));
NSArray<FBObjectInStructReference *> *references = FBGetReferencesForObjectsInStructEncoding(wrapper, encoding);
[result addObjectsFromArray:references];
}
else {
[result addObject:wrapper];
}
}
free(ivars);
return [result
copy];
}
|
获得所有引用后,再通过class_getIvarLayout
来提取强引用。
FBObjectiveCBlock
流程类似先获取引用然后过滤,只是获取强引用的方法是通过FBGetBlockStrongReferences
来实现的。判断是不是Block的方式是新建一个空的Block然后判断该对象的是不是其子类。具体获取方法_GetBlockStrongLayout
。
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
|
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.
*/
if ((blockLiteral->flags & BLOCK_HAS_CTOR)
|| !(blockLiteral->flags & BLOCK_HAS_COPY_DISPOSE)) {
return
nil;
}
void (*dispose_helper)(
void *src) = blockLiteral->descriptor->dispose_helper;
const size_t ptrSize =
sizeof(
void *);
const size_t elements = (blockLiteral->descriptor->size + ptrSize -
1) / ptrSize;
void *obj[elements];
void *detectors[elements];
for (size_t i =
0; i < elements; ++i) {
FBBlockStrongRelationDetector *detector = [FBBlockStrongRelationDetector new];
obj[i] = detectors[i] = detector;
}
@autoreleasepool {
dispose_helper(obj);
}
NSMutableIndexSet *layout = [
NSMutableIndexSet indexSet];
for (size_t i =
0; i < elements; ++i) {
FBBlockStrongRelationDetector *detector = (FBBlockStrongRelationDetector *)(detectors[i]);
if (detector.isStrong) {
[layout addIndex:i];
}
[detector trueRelease];
}
return layout;
}
|
通过block的size_t大小创建相同大小的数组类型,其对象类型是FBBlockStrongRelationDetector,然后调用dispose_helper
,根据判断是否调用了FBBlockStrongRelationDetector对象的release方法来判断是不是强引用,最后返回一个表示位置的数组,然后根据该数组获取具体的对象
这里使用了一个黑盒技术。创建一个对象来假扮想要调查的Block。因为知道Block的接口,知道在哪可以找到Block持有的引用。伪造的对象将会拥有“释放检测(release detectors)”来代替这些引用。释放检测器是一些很小的对象,这里是FBBlockStrongRelationDetector,它们会观察发送给它们的释放消息。当持有者想要放弃它的持有的时候,这些消息会发送给强引用对象。当释放伪造的对象的时候,可以检测哪些检测器接收到了这些消息。只要知道哪些索引在伪造的对象的检测器中,就可以找到原来Block中实际持有的对象。
FBObjectiveCNSCFTimer
主要是通过CFRunLoopTimerGetContext
获取CFRunLoopTimerContext
然后获取target
和userInfo
。
过滤配置器
FBObjectGraphConfiguration
是一个过滤的配置器,根据传入的Block来过滤的Block调用,以及是否检查NSTimer。传入FBGraphEdgeFilterBlock
的定义如下:
1
2
|
typedef FBGraphEdgeType (^FBGraphEdgeFilterBlock)(FBObjectiveCGraphElement *_Nullable fromObject,
FBObjectiveCGraphElement *
_Nullable toObject);
|
fromObject是传入的对象,toObject是传入对象引用的对象,根据指定的规则来判断是否需要过滤,过滤的话就相当于断掉两个节点之间的连线,来看看官方的使用例子:
1
2
3
4
5
6
7
8
9
10
11
|
NSMutableArray *filters = @[
FBFilterBlockWithObjectIvarRelation([
UIView
class],
@"_subviewCache"),
];
FBObjectGraphConfiguration *configuration =
[[FBObjectGraphConfiguration alloc] initWithFilterBlocks:filters
shouldInspectTimers:
YES];
FBRetainCycleDetector *detector = [[FBRetainCycleDetector alloc] initWithConfiguration:configuration];
[detector addCandidate:myObject];
NSSet *retainCycles = [detector findRetainCycles];
|
这里过滤的是UIView
类的_subviewCache
属性的引用。
总结
FBRetainCycleDetector
目前来说肯定还存在一些问题,还有引用关系比较复杂的话,DFS占用的内存还是挺大的。
转:http://www.alonemonkey.com/2016/05/15/fbretaincycledetector-analyse/