Xcode Instruments

一般来说,iOS的内存泄露检测大多是通过Instruments里面的Leaks。但在使用操作上有大量重复的手动操作。
1.打开Xcode,给性能分析(profiling)编译。
2.载入Instruments。
3.使用应用程序,尝试尽可能多的重现场景和行为。
4.查看内存和泄露。
5.追踪内存泄露的根源。
6.修复这个问题。

MLeaksFinder

MLeaksFinder 的使用很简单,基本上就是把 MLeaksFinder 目录下的文件添加到你的项目中,就可以在运行时(debug 模式下)帮助你检测项目里的内存泄露了,无需修改任何业务逻辑代码,而且只在 debug 下开启。
当发生内存泄露时,MLeaksFinder 会中断言,并准确的告诉哪个对象泄露了。在控制台以stack的形式打印出哪个view未被释放。
[MLeaksFinder:精准 iOS 内存泄露检测工具]

Github仓库地址:https://github.com/Tencent/MLeaksFinder

优点:

使用简单,不侵入业务逻辑代码,不用打开 Instrument
使用了 AOP 技术,hook 掉 UINavigationController 和 UINavigationController 的 pop 跟 push 方法等操作,可以参见源码
不需要额外的操作,你只需开发你的业务逻辑,在你运行调试时就能帮你检测
内存泄露发现及时,更改完代码后一运行即能发现
精准,能准确地告诉你哪个对象没被释放

原理:

从 UINavigationController 入手。当一个UINavigationController被pop或push后,该UINavigationController包括它的view,view的subviews等等将很快被释放(除非你把它设计成单例,或者持有它的强引用)。于是,我们只需在一个 ViewController 被 pop 或 push 一小段时间后,看看该 UINavigationController,它的 view,view 的 subviews 等等是否还存在。
具体的方法是,为基类 NSObject 添加一个方法 -willDealloc 方法,该方法的作用是,先用一个弱指针指向 self,并在一小段时间(3秒)后,通过这个弱指针调用 -assertNotDealloc ,而 -assertNotDealloc 主要作用是直接中断言。
这样,当我们认为某个对象应该要被释放了,在释放前调用这个方法,如果3秒后它被释放成功,weakSelf 就指向 nil,不会调用到 -assertNotDealloc 方法,也就不会中断言,如果它没被释放(泄露了), -assertNotDealloc 就会被调用中断言。这样,当一个 UINavigationController 被 pop 或 push 时(我们认为它应该要被释放了),我们遍历该 UINavigationController 上的所有 view,依次调 -willDealloc ,若3秒后没被释放,就会中断言。

不足:

只能检测view和viewController级别的泄露。为此,MLeaksFinder 提供了一个手动扩展的机制。宏 MLCheck() 做的事就是为传进来的对象建立 View-ViewController stack 信息,并对传进来的对象调用 -willDealloc 方法。而且只是通过hook相关方法,在延迟3秒来检测是否释放来判断有没有泄漏,显得处理方式有点简单粗暴。

FBMemoryProfiler

前言描述的诸多不便,而自动化可以在不需要更多开发者的情况下,更快的找到内存泄露。为了解决这个问题, FBMemoryProfiler应运而生。

在 Objective-C 中找循环引用类似于在一个有向无环图(directed acyclic graph)中找环, 而节点就是对象,边就是对象之间的引用(如果对象A持有对象B,那么,A到B之间就存在着引用)。我们的 Objective-C 对象已经在我们的图中,我们要做的就是用深度优先搜索遍历它。图中的节点可以是对象,也可以是Block。
Block和对象有一点不一样。运行时不会很轻易的看到它们的布局。具体解决方法参考链接。

运行时有很多工具允许我们对对象进行内省。
我们要做的第一件事是获取对象的实例变量的布局(ivar layout)。
1 const char class_getIvarLayout(Class cls);
2 const char
class_getWeakIvarLayout(Class cls);
对于对象,实例变量的布局描述了我们在哪儿可以找到其他对象的引用。它会提供给我们一个索引(index),这代表我们需要在对象地址上添加一个偏移量(offset),就可以得到它所引用的对象的地址。运行时也允许我们获取“弱引用实例变量布局(weak ivar layout)”。

生成(Generations)

FBMemoryProfiler的一个很伟大的特性是“生成追踪(generation tracking)”,类似于苹果的Instruments的生成追踪。生成只是简单的在两次标记之间拍摄所有仍然活着的对象的快照。

Github地址:https://github.com/facebook/FBMemoryProfiler

对比

MLeaksFinder 采用 hook UINavigationController 的 pop、push 及 UIViewController 的viewDidDisappear 、viewWillAppear、和 dismissViewControllerAnimated:completion: 方法,再延后3秒来检测view是否释放的方式;
FBMemoryProfiler采用运行时对象内省,通过查找对象的实例变量,采用深度优先算法搜索遍历来查找是否存在循环,此种方式显得更靠谱。但目前的版本还比较低,才推出不久,界面上的使用还不太稳定有些许卡顿,参考文档之类的也比较少。相信Facebook工程师在以后能把这个项目做得更好。

参考链接:
https://github.com/Zepo/MLeaksFinder
https://github.com/facebook/FBMemoryProfiler
https://code.facebook.com/posts/583946315094347/automatic-memory-leak-detection-on-ios/(Facebook官方介绍)
http://www.cocoachina.com/ios/20160419/15954.html(译文)