基本原理:

MlLeaksFinder是app运行的过程中,检测内存泄漏的第三方库,可以帮助在代码调试阶段发现问题。通过method swizzled hook 对象生命周期的方法,在对象结束生命周期的时候,在指定时间之后给对象发送某个消息。如果这个时候对象已经被释放,消息不会被执行,如果没有释放说明发生了内存泄漏,消息就会被执行,从而提醒开发人员。通过递归的方式,会记录下某个视图或者controller的树形节点的位置,能更好的帮助定位到具体哪个对象没有被释放。MLLeaksFinder引入了FBRetainCycleDetector,可以检查循环引用。

内存泄漏分为2种,第1种是对象没有被任何引用,在内存中没有被释放。第2种是对象发生循环引用,无法被释放。在RAC的场景下,通过是2引起的内存泄漏。

源码分析:

MLeaksFinder.h定义了MLeaksFinder中使用的宏

MLeaksFinder.h

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
#ifdef MEMORY_LEAKS_FINDER_ENABLED


//_INTERNAL_MLF_ENABLED 宏用来控制 MLLeaksFinder库
//什么时候开启检测,可以自定义这个时机,默认则是在DEBUG模式下会启动,RELEASE模式下不启动
//它是通过预编译来实现的
#define _INTERNAL_MLF_ENABLED MEMORY_LEAKS_FINDER_ENABLED

#else

#define _INTERNAL_MLF_ENABLED DEBUG

#endif

//_INTERNAL_MLF_RC_ENABLED 宏用来控制 是否开启循环引用的检测
#ifdef MEMORY_LEAKS_FINDER_RETAIN_CYCLE_ENABLED

#define _INTERNAL_MLF_RC_ENABLED MEMORY_LEAKS_FINDER_RETAIN_CYCLE_ENABLED


//COCOAPODS 因为MLLeaksFinder引用了第三库用来检查循环引用,所以必须是当前项目中使用了COCOAP
//ODS,才能使用这个功能。
#elif COCOAPODS

#define _INTERNAL_MLF_RC_ENABLED COCOAPODS

#endif

MLLeakeObjectProxy用来对泄漏对象检查循环引用

MLeakedObjectProxy

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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
//用来检查当前泄漏对象是否已经添加到泄漏对象集合中,如果是,就不再添加也不再提示开发者
+ (BOOL)isAnyObjectLeakedAtPtrs:(NSSet *)ptrs
{
NSAssert([NSThread isMainThread], @"Must be in main thread.");

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//全局用于保存泄漏对象的集合
leakedObjectPtrs = [[NSMutableSet alloc] init];
});

if (!ptrs.count) {
return NO;
}
//NSSet求交集
if ([leakedObjectPtrs intersectsSet:ptrs]) {
return YES;
} else {
return NO;
}
}
+ (void)addLeakedObject:(id)object {

NSAssert([NSThread isMainThread], @"Must be in main thread.");


//创建用于检查循环引用的objectProxy对象
MLeakedObjectProxy *proxy = [[MLeakedObjectProxy alloc] init];

proxy.object = object;

proxy.objectPtr = @((uintptr_t)object);

proxy.viewStack = [object viewStack];

static const void *const kLeakedObjectProxyKey = &kLeakedObjectProxyKey;

objc_setAssociatedObject(object, kLeakedObjectProxyKey, proxy,
OBJC_ASSOCIATION_RETAIN);

[leakedObjectPtrs addObject:proxy.objectPtr];



#if _INTERNAL_MLF_RC_ENABLED
//带有循环引用检查功能的提示框
[MLeaksMessenger alertWithTitle:@"Memory Leak"

message:[NSString stringWithFormat:@"%@", proxy.
viewStack]

delegate:proxy

additionalButtonTitle:@"Retain Cycle"];

#else
//普通提示框
[MLeaksMessenger alertWithTitle:@"Memory Leak"

message:[NSString stringWithFormat:@"%@", proxy.
viewStack]];

#endif

}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)
buttonIndex {

#if _INTERNAL_MLF_RC_ENABLED

dispatch_async(dispatch_get_global_queue(0, 0), ^{

FBRetainCycleDetector *detector = [FBRetainCycleDetector new];

[detector addCandidate:self.object];

NSSet *retainCycles = [detector findRetainCyclesWithMaxCycleLength:20];

BOOL hasFound = NO;

//retainCycles中是找到的所有循环引用的链
for (NSArray *retainCycle in retainCycles) {

NSInteger index = 0;

for (FBObjectiveCGraphElement *element in retainCycle) {
//找到当前内存泄漏对象所在的循环引用的链
if (element.object == object) {
//把当前对象调整到第一个的位置,方便查看
NSArray *shiftedRetainCycle = [self shiftArray:retainCycle
toIndex:index];

dispatch_async(dispatch_get_main_queue(), ^{

[MLeaksMessenger alertWithTitle:@"Retain Cycle"

message:[NSString
stringWithFormat:@"%@",
shiftedRetainCycle]];

});

hasFound = YES;

break;

}
++index;
}

if (hasFound) {
break;
}
}

if (!hasFound) {

dispatch_async(dispatch_get_main_queue(), ^{

[MLeaksMessenger alertWithTitle:@"Retain Cycle"

message:@"Fail to find a retain cycle"];

});
}
});

#endif

}

NSObject+MemoryLeak主要功能存储对象的父子节点的树形结构,method swizzle逻辑 ,白名单以及判断对象是否发生内存泄漏

NSObject+MemoryLeak

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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
- (BOOL)willDealloc {

NSString *className = NSStringFromClass([self class]);
//通过白名单可以配置哪些对象不纳入检查,例如一些单例
if ([[NSObject classNamesInWhiteList] containsObject:className])

return NO;

NSNumber *senderPtr = objc_getAssociatedObject([UIApplication sharedApplication], kLatestSenderKey);

if ([senderPtr isEqualToNumber:@((uintptr_t)self)])

return NO;

__weak id weakSelf = self;
//在特定时间检查对象是否已经发生内存泄漏
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)
), dispatch_get_main_queue(), ^{

__strong id strongSelf = weakSelf;
//如果对象已经被释放,strongSelf为nil 调用该方法什么也不发生
[strongSelf assertNotDealloc];

});

return YES;
}

//改方法被调用说明改对象已经发生内存泄漏
- (void)assertNotDealloc {
//检查是否已经记录,如果是,不再提示用户
if ([MLeakedObjectProxy isAnyObjectLeakedAtPtrs:[self parentPtrs]]) {

return;

}

[MLeakedObjectProxy addLeakedObject:self];

NSString *className = NSStringFromClass([self class]);

NSLog(@"Possibly Memory Leak.\nIn case that %@ should not be dealloced,
override -willDealloc in %@ by returning NO.\nView-ViewController stack: %@
", className, className, [self viewStack]);

}

//主要通过递归来记录每个节点在树形结构中的位置,以及父子节点的指针
- (void)willReleaseChildren:(NSArray *)children {

NSArray *viewStack = [self viewStack];

NSSet *parentPtrs = [self parentPtrs];

for (id child in children) {

NSString *className = NSStringFromClass([child class]);

[child setViewStack:[viewStack arrayByAddingObject:className]];

[child setParentPtrs:[parentPtrs setByAddingObject:@((uintptr_t)child)]];

[child willDealloc];
}
}
+ (void)swizzleSEL:(SEL)originalSEL withSEL:(SEL)swizzledSEL {


//通过预编译控制是否hook方法
#if _INTERNAL_MLF_ENABLED


//通过预编译控制是否检查循环引用
#if _INTERNAL_MLF_RC_ENABLED

// Just find a place to set up FBRetainCycleDetector.

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

dispatch_async(dispatch_get_main_queue(), ^{

[FBAssociationManager hook];

});

});

#endif



Class class = [self class];



Method originalMethod = class_getInstanceMethod(class, originalSEL);

Method swizzledMethod = class_getInstanceMethod(class, swizzledSEL);



BOOL didAddMethod =
//class_addMethod主要是用来给某个类添加一个方法,originalSEL相当于是方法名称,method_getIm
//plementtation是方法实现, 它返回一个BOOL类型的值
//在当前class中没有叫originalSEL的方法(
//具体不是看interface里没有没有声明,而是看implementaion文件里有没有方法实现),
// 并且有swizzledMethod方法的实现
//这个时候该函数会返回true,其他情况均返回false
class_addMethod(class,

originalSEL,

method_getImplementation(swizzledMethod),

method_getTypeEncoding(swizzledMethod));



if (didAddMethod) {
//didAddMethod为true 说明swizzledMethod之前不存在,通过class_addMethod函数添加了一个名字叫origninalSEL,实现是swizzledMoethod函数。
class_replaceMethod(class,

swizzledSEL,

method_getImplementation(originalMethod),

method_getTypeEncoding(originalMethod));

} else {
//didAddMethod为false 说明swizzledMethod方法已经存在,直接交换二者实现
method_exchangeImplementations(originalMethod, swizzledMethod);

}

#endif

}

UINavigationController + MemoryLeak 主要是通过UINavigationController的方法去检测子UIViewController页面的生命周期,UIViewController的生命周期由UINavigationController的方法和UIViewController自身的一些方法共同决定的。

UINavigationController + MemoryLeak

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
89
90
91
92
93
//现在在具体的类型中添加方法hook,加载load中并且调用dspatch_once来保证只初始化一次,load是必然会调用的,并且category的load方法调用和类自身的load方法调用是分开的,互不干扰。
+ (void)load {

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

[self swizzleSEL:@selector(pushViewController:animated:) withSEL:@
selector(swizzled_pushViewController:animated:)];

[self swizzleSEL:@selector(popViewControllerAnimated:) withSEL:@
selector(swizzled_popViewControllerAnimated:)];

[self swizzleSEL:@selector(popToViewController:animated:) withSEL:@
selector(swizzled_popToViewController:animated:)];

[self swizzleSEL:@selector(popToRootViewControllerAnimated:) withSEL:@
selector(swizzled_popToRootViewControllerAnimated:)];

});
}

- (void)swizzled_pushViewController:(UIViewController *)viewController
animated:(BOOL)animated {

if (self.splitViewController) {
//这里主要是考虑到app中有使用splitViewController的情况的时候,下一个根页面push之后,
//之前被pop的根页面才会回收
id detailViewController = objc_getAssociatedObject(self,
kPoppedDetailVCKey);

if ([detailViewController isKindOfClass:[UIViewController class]]) {
//回收之前被pop的根页面
[detailViewController willDealloc];

objc_setAssociatedObject(self, kPoppedDetailVCKey, nil,
OBJC_ASSOCIATION_RETAIN);

}

}
[self swizzled_pushViewController:viewController animated:animated];

}
- (UIViewController *)swizzled_popViewControllerAnimated:(BOOL)animated {

UIViewController *poppedViewController = [self swizzled_popViewControllerAnimated:animated];

if (!poppedViewController) {
return nil;
}

//当前页面是spliteViewController根页面
if (self.splitViewController &&

self.splitViewController.viewControllers.firstObject == self &&

self.splitViewController == poppedViewController.splitViewController) {

objc_setAssociatedObject(self, kPoppedDetailVCKey, poppedViewController
, OBJC_ASSOCIATION_RETAIN);

return poppedViewController;
}


// VC is not dealloced until disappear when popped using a left-edge swipe gesture

extern const void *const kHasBeenPoppedKey;

objc_setAssociatedObject(poppedViewController, kHasBeenPoppedKey, @(YES),
OBJC_ASSOCIATION_RETAIN);

return poppedViewController;

}

- (NSArray<UIViewController *> *)swizzled_popToViewController:(
UIViewController *)viewController animated:(BOOL)animated {

NSArray<UIViewController *> *poppedViewControllers = [self
swizzled_popToViewController:viewController animated:animated];

//一次性pop多个页面的时候,这些页面的viewDidDisappear估计都没有被调用,直接回收了
for (UIViewController *viewController in poppedViewControllers) {

[viewController willDealloc];

}

return poppedViewControllers;

}

UIViewController + MemoryLeak

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
- (void)swizzled_viewDidDisappear:(BOOL)animated {
[self swizzled_viewDidDisappear:animated];
//仅仅当是pop引起viewDidDisappear的时候才释放(当被挡住之后也会调用ViewDidDisappear)
if ([objc_getAssociatedObject(self, kHasBeenPoppedKey) boolValue]) {

[self willDealloc];

}

}

- (void)swizzled_dismissViewControllerAnimated:(BOOL)flag
completion:(void (^)(void))completion {

[self swizzled_dismissViewControllerAnimated:flag completion:completion];

//dismiss掉presentedViewController,释放它 (但是什么时候当前viewController被释放呢)
UIViewController *dismissedViewController = self.presentedViewController;

if (!dismissedViewController && self.presentingViewController) {
//释放自己
dismissedViewController = self;
}

if (!dismissedViewController) return;
//以present出来的viewcontroller,不通过DidDisappear去判断是否释放了
[dismissedViewController willDealloc];

}