学计算机的那个

不是我觉到、悟到,你给不了我,给了也拿不住;只有我觉到、悟到,才有可能做到,能做到的才是我的.

0%

MLeadksFinder使用

MLeaksFinder

作用

MLeaksFinder helps you find memory leaks in your iOS apps at develop time

优点 VS Instrument

MLeaksFinder 具备以下优点:

  1. 使用简单,不侵入业务逻辑代码,不用打开 Instrument
  2. 不需要额外的操作,你只需开发你的业务逻辑,在你运行调试时就能帮你检测
  3. 内存泄露发现及时,更改完代码后一运行即能发现(这点很重要,你马上就能意识到哪里写错了)
  4. 精准,能准确地告诉你哪个对象没被释放

使用

1
2
3
4
5
6
7
Memory Leak
(
MyTableViewController,
UITableView,
UITableViewWrapperView,
MyTableViewCell
)

MLeaksFinder can automatically find leaks in UIView and UIViewController objects. 通过断言的方式弹窗展示未被释放的UIView或UIViewController对象

关闭

一些不应该被释放的对象比如全局对象,static,单例,又不想被弹窗误报override

1
2
3
- (BOOL)willDealloc {
return NO;
}

实现

Podspec文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Pod::Spec.new do |s|
s.name = "MLeaksFinder"
s.version = "1.0.0"
s.summary = "Find memory leaks in your iOS app at develop time."
s.homepage = "https://github.com/Zepo/MLeaksFinder"
# s.screenshots = "www.example.com/screenshots_1", "www.example.com/screenshots_2"
s.license = 'MIT'
s.author = { "Zeposhe" => "zeposhe@163.com" }
s.source = { :git => "https://github.com/Zepo/MLeaksFinder.git", :tag => s.version }
# s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'
s.ios.deployment_target = '6.0'
s.source_files = 'MLeaksFinder/**/*'
# s.resource_bundles = {
# 'MLeaksFinder' => ['MLeaksFinder/Assets/*.png']
# }

s.public_header_files = 'MLeaksFinder/MLeaksFinder.h', 'MLeaksFinder/NSObject+MemoryLeak.h'
# s.frameworks = 'UIKit', 'MapKit'
# s.dependency 'FBRetainCycleDetector'
end
1
2
3
重要: FBRetainCycleDetector is removed from the podspec due to Facebook's BSD-plus-Patents license. If you 
want to use FBRetainCycleDetector to find retain cycle, add pod 'FBRetainCycleDetector' to your project's
Podfile and turn the macro MEMORY_LEAKS_FINDER_RETAIN_CYCLE_ENABLED on in MLeaksFinder.h.
willDealloc方法

为基类 NSObject 添加一个方法 -willDealloc 方法,该方法的作用是,先用一个弱指针指向 self,并在一小段时间(3秒)后,通过这个弱指针调用 -assertNotDealloc,而 -assertNotDealloc 主要作用是直接中断言。

1
2
3
4
5
6
7
8
9
10
- (BOOL)willDealloc {
__weak id weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[weakSelf assertNotDealloc];
});
return YES;
}
- (void)assertNotDealloc {
NSAssert(NO, @“”);
}

果3秒后它被释放成功,weakSelf 就指向 nil,不会调用到 -assertNotDealloc 方法,也就不会中断言,如果它没被释放(泄露了),-assertNotDealloc 就会被调用中断言

FBRetainCycleDetector

An iOS library that finds retain cycles using runtime analysis
用于检测引起内存泄漏对象的环形引用链

使用

1
2
3
4
FBRetainCycleDetector *detector = [FBRetainCycleDetector new];
[detector addCandidate:myObject];
NSSet *retainCycles = [detector findRetainCycles];
NSLog(@"%@", retainCycles);

如何检测饮用成环

点击 “Retain Cycle” 按钮,MLeaksFinder 将调用 FBRetainCycleDetector 进行详细问题检测

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
#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;
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对象循环引用

FBRetainCycleDetector 基于外部传入的object 以及查找深度,进行深度优先遍历所有强引用属性,和动态运行时关联的强引用属性,同时将这些 关联对象的地址 放入 objectSet (set)的集合中, 将对象信息计入 objectOnPath 集合中(set), 并且将对象在对象栈 stack 中存储一份,便于查找对应环。

首先判断 如果传入的 object 是 NSObject 的话,获取对象的 class,使用

1
const char *class_getIvarLayout(Class cls);

获取 class 的所有定义的 property 的布局信息,取出 object 对应的 property 的value值,将value 对象的地址(数字)加入 objectSet 中,将对象指针加入到 objectOnPath,在整个树形遍历中,如果遍历到的新节点,在原来的 objectSet 地址表中已经存在了,代表形成了引用环,即原本的树形结构连成了图。此时可以根据 stack中记录的路径,结合 重复的 object构建一个环形图,作为环形引用链返回。

树是没有环的图

NSBlock类型对像

在C的结构体中是不存在强弱引用区分的,在编译期,编译器会将所谓的强引用通过一个 copy_helper 的function 做copy 操作,并为 block 生成的 struct 构造 dispose_helper 的 function,dispose_helper 负责在 struct 将要释放时,去释放它所引用的对象。下面是编译器生成的 dispose_helper function 的定义 ,入参为 struct 的地址 _Block_object_dispose 是编译器的 funtion

1
2
3
4
void __block_dispose_4(struct __block_literal_4 *src) {
// was _Block_destroy
_Block_object_dispose(src->existingBlock, BLOCK_FIELD_IS_BLOCK);
}

作者利用黑盒测试,基于原有的 block对象 ,拿到对应block对象的 descriptor指针 ,descriptor记录了block对象释放的时候要执行的 dispose_helper 方法和block对象所有引用对象的数组,
这个数组包括了强引用对象和弱应用对象 *src。 也就是说,block被释放时,执行的 dispose_helper 方法的入参 是 *scr;那么只需要伪装一个被引用的数组,传入dispose_helper 做个测试,数组中哪一个对象呗调用了 release 方法,那么谁就是被强引用的,记住src对应下标的地址就好。

注意

特殊情况

对于某些特殊情况,释放的时机不大一样(比如系统手势返回时,在划到一半时 hold 住,虽然已被 pop,但这时还不会被释放,ViewController 要等到完全 disappear 后才释放),需要做特殊处理,具体的特殊处理视具体情况而定。

系统View

某些系统的私有 View,不会被释放(可能是系统 bug 或者是系统出于某些原因故意这样做的,这里就不去深究了),因此需要建立白名单

手动扩展

MLeaksFinder目前只检测 ViewController 跟 View 对象。为此,MLeaksFinder 提供了一个手动扩展的机制,你可以从 UIViewController 跟 UIView 出发,去检测其它类型的对象的内存泄露。如下所示,我们可以检测 UIViewController 底下的 View Model:

1
2
3
4
5
6
7
- (BOOL)willDealloc {
if (![super willDealloc]) {
return NO;
}
MLCheck(self.viewModel);
return YES;
}

参考

  1. MLeaksFinder:精准 iOS 内存泄露检测工具
  2. FBRetainCycleDetector + MLeaksFinder 阅读