MLeaksFinder

MLeaksFinder

是 iOS 平台的自动内存泄漏检测工具,引进 MLeaksFinder 后,就可以在日常的开发,调试业务逻辑的过程中自动地发现并警告内存泄漏。开发者无需打开 instrument 等工具,也无需为了找内存泄漏而去跑额外的流程。并且,由于开发者是在修改代码之后一跑业务逻辑就能发现内存泄漏的,这使得开发者能很快地意识到是哪里的代码写得问题。这种及时的内存泄漏的发现在很大的程度上降低了修复内存泄漏的成本。

MLeaksFinder 0.1 开源已经有一段时间,关于 MLeaksFinder 的基本原理,可以参考这篇文章。在 MLeaksFinder 开源之后,收到的最多的反馈是:MLeaksFinder 帮忙发现了内存泄漏,但是要去修复这些内存泄漏,找到造成问题的代码很难,特别是对于历史遗留的内存泄漏。

现在,MLeaksFinder 0.2 来了。如果说 0.1 版本旨在帮助开发者发现内存泄漏,那么 0.2 版本的新特性,正是旨在帮助开发者更好地解决内存泄漏。MLeaksFinder 0.2 包括以下几个新特性:

  • assert 改为 alert
  • 追踪对象的生命周期
  • 查找循环引用链

下面,我们来逐一看一下这几个特性。

<a id="more" style="background-color: rgb(255, 255, 255); color: rgb(85, 85, 85); text-decoration: none; border-bottom: 1px solid rgb(204, 204, 204); word-wrap: break-word; font-family: Lato, "PingFang SC", "Microsoft YaHei", sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;"></a>

assert 改为 alert

在 MLeaksFiner 0.1 版本,当 MLeaksFinder 发现内存泄漏时,会直接中 assert 并打出内存泄漏的信息。Assert 能迫使开发者及时地去修复内存泄漏,并且,如果只是打日志,内存泄漏的日志很可能会被淹没在众多的日志中。这种 assert 的方法在我们实际的项目取得了不错的效果。

然而,assert 确实也有不好的一面。当开发者在调试业务逻辑的过程中,如果由于内存泄漏中 assert 而使得整个程序挂掉了,那么开发者的思维会因此被打断,并不得不在修复完内存泄漏之后,从头开始调试业务逻辑。有时候开发者更希望的是连贯地调完整个业务逻辑之后,再回过头来修复内存泄漏。

因此,MLeaksFinder 0.2 把 assert 改成了 alert。当发现内存泄漏之后,开发者可以把 alert 框关掉,并继续调试业务逻辑。而且,把 assert 改成 alert 之后,也使得进一步分析内存成为可能,为下面两个新特性垫定基础。

追踪对象的生命周期

当发现可能的内存泄漏对象并给出 alert 之后,MLeaksFinder 会进一步地追踪该对象的生命周期,并在该对象释放时给出

Object Deallocated

的 alert。

为什么认为一个对象内存泄漏之后,还要进一步去追踪该对象后续会不会释放呢?MLeaksFinder 的基本原理是这样的,当一个 ViewController 被 pop 或 dismiss 之后,我们认为该 ViewController,包括它上面的子 ViewController,以及它的 View,View 的 subView 等等,都很快会被释放,如果某个 View 或者 ViewController 没释放,我们就认为该对象泄漏了。然而,这样的判断内存泄漏的方法存在两个可能的“误判”:

1) 单例或者被 cache 起来复用的 View 或 ViewController

对于这样的 View 或 ViewController,在被 pop 或 dismiss 之后是不会被释放的。然而,由于 View 相关的对象一般都占用了较多了内存,这样的设计通常来说不是好的设计。如果开发者由于性能问题等原因而不得不这样设计的时候,开发者可以在报泄漏的类里重载

- (BOOL)willDealloc

方法,直接

return NO;

以消除内存泄漏的警告,这个消除内存泄漏警告的方法与 MLeaksFinder 0.1 版本一致。

2) 释放不及时的 View 或 ViewController

例如,发起网络请求的时候,在网络请求回调的 block 里强引用 ViewController,以便在网络请求回来的时候刷新界面。在网络请求比较慢的情况下,这种做法存在两个问题:

  • ViewController 被 pop 之后,由于被 block 强引用导致释放不及时
  • ViewController 被 pop 之后,如果网络请求回来了,不应该继续做刷新界面的事,浪费 CPU

所以,对于这种情况,我们应该在 block 里弱引用 ViewController,而不是强引用。

下面我们来看如何利用对象的生命周期来分析内存的真正使用情况,分三种情况:

1) 单例或者被 cache 起来复用

如下面所示,在第一次 pop 的时候报 Memory Leak,在之后的重复 push 并 pop 同一个 ViewController 过程中,即不报 Object Deallocated,也不报 Memory Leak。这种情况下我们可以确定该对象被设计成单例或者 cache 起来了。

<figure class="highlight objc" style="display: block; margin: 20px 0px; background: rgb(29, 31, 33); padding: 15px; overflow: auto; font-size: 13px; color: rgb(197, 200, 198); line-height: 1.6; font-family: Lato, "PingFang SC", "Microsoft YaHei", sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">

|

<pre style="overflow: auto; font-family: "Input Mono", "PT Mono", Consolas, Monaco, Menlo, monospace; font-size: 13px; background: rgb(29, 31, 33); margin: 0px; padding: 1px 20px 1px 1px; color: rgb(102, 102, 102); line-height: 1.6; border: none; text-align: right;">

1

2

</pre>

|

<pre style="overflow: auto; font-family: "Input Mono", "PT Mono", Consolas, Monaco, Menlo, monospace; font-size: 13px; background: rgb(29, 31, 33); margin: 0px; padding: 1px; color: rgb(197, 200, 198); line-height: 1.6; border: none;">

pop             push           pop           push          pop

----------> Leak ----------> | ----------> | ----------> | ---------->

</pre>

|

</figure>

2) 释放不及时

如下面所示,在第一次 pop 的时候报 Memory Leak,在之后的重复 push 并 pop 同一个 ViewController 过程中,对于同一个类不断地报 Object Deallocated 和 Memory Leak。这种情况属于释放不及时的情况。

<figure class="highlight objc" style="display: block; margin: 20px 0px; background: rgb(29, 31, 33); padding: 15px; overflow: auto; font-size: 13px; color: rgb(197, 200, 198); line-height: 1.6; font-family: Lato, "PingFang SC", "Microsoft YaHei", sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">

|

<pre style="overflow: auto; font-family: "Input Mono", "PT Mono", Consolas, Monaco, Menlo, monospace; font-size: 13px; background: rgb(29, 31, 33); margin: 0px; padding: 1px 20px 1px 1px; color: rgb(102, 102, 102); line-height: 1.6; border: none; text-align: right;">

1

2

</pre>

|

<pre style="overflow: auto; font-family: "Input Mono", "PT Mono", Consolas, Monaco, Menlo, monospace; font-size: 13px; background: rgb(29, 31, 33); margin: 0px; padding: 1px; color: rgb(197, 200, 198); line-height: 1.6; border: none;">

pop             push                 pop             push                 pop

----------> Leak ----------> Dealloc ----------> Leak ----------> Dealloc ----------> Leak

</pre>

|

</figure>

3) 真正的内存泄漏

如下面所示,在第一次 pop 的时候报 Memory Leak,在之后的重复 push 并 pop 同一个 ViewController 过程中,不报 Object Deallocated,但每次 pop 之后又报 Memory Leak。这种情况下每回进入并退出一个页面后,就报有新的内存泄漏,同时被报泄漏的对象又从来没有释放过,可以确定是真正的内存泄漏。

<figure class="highlight objc" style="display: block; margin: 20px 0px; background: rgb(29, 31, 33); padding: 15px; overflow: auto; font-size: 13px; color: rgb(197, 200, 198); line-height: 1.6; font-family: Lato, "PingFang SC", "Microsoft YaHei", sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">

|

<pre style="overflow: auto; font-family: "Input Mono", "PT Mono", Consolas, Monaco, Menlo, monospace; font-size: 13px; background: rgb(29, 31, 33); margin: 0px; padding: 1px 20px 1px 1px; color: rgb(102, 102, 102); line-height: 1.6; border: none; text-align: right;">

1

2

</pre>

|

<pre style="overflow: auto; font-family: "Input Mono", "PT Mono", Consolas, Monaco, Menlo, monospace; font-size: 13px; background: rgb(29, 31, 33); margin: 0px; padding: 1px; color: rgb(197, 200, 198); line-height: 1.6; border: none;">

pop             push           pop             push           pop

----------> Leak ----------> | ----------> Leak ----------> | ----------> Leak

</pre>

|

</figure>

查找循环引用链

Facebook 在前阵子开源了一个循环引用检测工具

FBRetainCycleDetector。当传入内存中的任意一个 OC 对象,FBRetainCycleDetector 会递归遍历该对象的所有强引用的对象,以检测以该对象为根结点的强引用树有没有循环引用。

我们知道,很多循环引用是 block 的使用不当造成的。而 FBRetainCycleDetector 最大的技术亮点,正在于如何找出一个 block 的所有强引用对象。对于这个感兴趣的,可以看 facebook 的这篇文章

然而,FBRetainCycleDetector 的使用存在两个问题:

  • 需要找到候选的检测对象
  • 检测循环引用比较耗时

正是由于这两个问题,FBRetainCycleDetector 通常是结合其它工具一起使用,通过其它工具先找出候选的检测对象,然后进行有选择的检测。当 MLeaksFinder 与 FBRetainCycleDetector 结合使用时,正好能达到很好的效果。我们先通过 MLeaksFinder 找到内存泄漏的对象,然后再过 FBRetainCycleDetector 检测该对象有没有循环引用即可。

循环引用的输出信息如下:

<figure class="highlight objc" style="display: block; margin: 20px 0px; background: rgb(29, 31, 33); padding: 15px; overflow: auto; font-size: 13px; color: rgb(197, 200, 198); line-height: 1.6; font-family: Lato, "PingFang SC", "Microsoft YaHei", sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">

|

<pre style="overflow: auto; font-family: "Input Mono", "PT Mono", Consolas, Monaco, Menlo, monospace; font-size: 13px; background: rgb(29, 31, 33); margin: 0px; padding: 1px 20px 1px 1px; color: rgb(102, 102, 102); line-height: 1.6; border: none; text-align: right;">

1

2

3

4

</pre>

|

<pre style="overflow: auto; font-family: "Input Mono", "PT Mono", Consolas, Monaco, Menlo, monospace; font-size: 13px; background: rgb(29, 31, 33); margin: 0px; padding: 1px; color: rgb(197, 200, 198); line-height: 1.6; border: none;">

(

"-> MyTableViewCell "

,

"-> _callback -> NSMallocBlock "

)

</pre>

|

</figure>

上面的信息表示,MyTableViewCell

有一个强引用的成员变量

_callback,该变量的类型是

__NSMallocBlock__,在

_callback

里,又强引用了

MyTableViewCell

造成循环引用。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,277评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,689评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,624评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,356评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,402评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,292评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,135评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,992评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,429评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,636评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,785评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,492评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,092评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,723评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,858评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,891评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,713评论 2 354

推荐阅读更多精彩内容