MLeaksFinder 新特性
原文 http://wereadteam.github.io/2016/07/20/MLeaksFinder2/
主題 iOS開(kāi)發(fā)
MLeaksFinder 是 iOS 平臺(tái)的自動(dòng)內(nèi)存泄漏檢測(cè)工具沈善,引進(jìn) MLeaksFinder 后碘举,就可以在日常的開(kāi)發(fā)莫瞬,調(diào)試業(yè)務(wù)邏輯的過(guò)程中自動(dòng)地發(fā)現(xiàn)并警告內(nèi)存泄漏。開(kāi)發(fā)者無(wú)需打開(kāi) instrument 等工具,也無(wú)需為了找內(nèi)存泄漏而去跑額外的流程待讳。并且品擎,由于開(kāi)發(fā)者是在修改代碼之后一跑業(yè)務(wù)邏輯就能發(fā)現(xiàn)內(nèi)存泄漏的,這使得開(kāi)發(fā)者能很快地意識(shí)到是哪里的代碼寫(xiě)得問(wèn)題。這種及時(shí)的內(nèi)存泄漏的發(fā)現(xiàn)在很大的程度上降低了修復(fù)內(nèi)存泄漏的成本堡纬。
MLeaksFinder 0.1 開(kāi)源已經(jīng)有一段時(shí)間聂受,關(guān)于 MLeaksFinder 的基本原理,可以參考這篇文章烤镐。在 MLeaksFinder 開(kāi)源之后蛋济,收到的最多的反饋是:MLeaksFinder 幫忙發(fā)現(xiàn)了內(nèi)存泄漏,但是要去修復(fù)這些內(nèi)存泄漏炮叶,找到造成問(wèn)題的代碼很難碗旅,特別是對(duì)于歷史遺留的內(nèi)存泄漏。
現(xiàn)在镜悉,MLeaksFinder 0.2 來(lái)了祟辟。如果說(shuō) 0.1 版本旨在幫助開(kāi)發(fā)者發(fā)現(xiàn)內(nèi)存泄漏,那么 0.2 版本的新特性侣肄,正是旨在幫助開(kāi)發(fā)者更好地解決內(nèi)存泄漏旧困。MLeaksFinder 0.2 包括以下幾個(gè)新特性:
assert 改為 alert
追蹤對(duì)象的生命周期
查找循環(huán)引用鏈
下面,我們來(lái)逐一看一下這幾個(gè)特性稼锅。
assert 改為 alert
在 MLeaksFiner 0.1 版本吼具,當(dāng) MLeaksFinder 發(fā)現(xiàn)內(nèi)存泄漏時(shí),會(huì)直接中 assert 并打出內(nèi)存泄漏的信息矩距。Assert 能迫使開(kāi)發(fā)者及時(shí)地去修復(fù)內(nèi)存泄漏拗盒,并且,如果只是打日志锥债,內(nèi)存泄漏的日志很可能會(huì)被淹沒(méi)在眾多的日志中陡蝇。這種 assert 的方法在我們實(shí)際的項(xiàng)目取得了不錯(cuò)的效果。
然而哮肚,assert 確實(shí)也有不好的一面登夫。當(dāng)開(kāi)發(fā)者在調(diào)試業(yè)務(wù)邏輯的過(guò)程中,如果由于內(nèi)存泄漏中 assert 而使得整個(gè)程序掛掉了允趟,那么開(kāi)發(fā)者的思維會(huì)因此被打斷悼嫉,并不得不在修復(fù)完內(nèi)存泄漏之后,從頭開(kāi)始調(diào)試業(yè)務(wù)邏輯拼窥。有時(shí)候開(kāi)發(fā)者更希望的是連貫地調(diào)完整個(gè)業(yè)務(wù)邏輯之后戏蔑,再回過(guò)頭來(lái)修復(fù)內(nèi)存泄漏。
因此鲁纠,MLeaksFinder 0.2 把 assert 改成了 alert总棵。當(dāng)發(fā)現(xiàn)內(nèi)存泄漏之后,開(kāi)發(fā)者可以把 alert 框關(guān)掉改含,并繼續(xù)調(diào)試業(yè)務(wù)邏輯情龄。而且,把 assert 改成 alert 之后,也使得進(jìn)一步分析內(nèi)存成為可能骤视,為下面兩個(gè)新特性墊定基礎(chǔ)鞍爱。
追蹤對(duì)象的生命周期
當(dāng)發(fā)現(xiàn)可能的內(nèi)存泄漏對(duì)象并給出 alert 之后,MLeaksFinder 會(huì)進(jìn)一步地追蹤該對(duì)象的生命周期专酗,并在該對(duì)象釋放時(shí)給出 Object Deallocated 的 alert睹逃。
為什么認(rèn)為一個(gè)對(duì)象內(nèi)存泄漏之后,還要進(jìn)一步去追蹤該對(duì)象后續(xù)會(huì)不會(huì)釋放呢祷肯?MLeaksFinder 的基本原理是這樣的沉填,當(dāng)一個(gè) ViewController 被 pop 或 dismiss 之后,我們認(rèn)為該 ViewController佑笋,包括它上面的子 ViewController翼闹,以及它的 View,View 的 subView 等等蒋纬,都很快會(huì)被釋放猎荠,如果某個(gè) View 或者 ViewController 沒(méi)釋放,我們就認(rèn)為該對(duì)象泄漏了蜀备。然而关摇,這樣的判斷內(nèi)存泄漏的方法存在兩個(gè)可能的“誤判”:
- 單例或者被 cache 起來(lái)復(fù)用的 View 或 ViewController
對(duì)于這樣的 View 或 ViewController,在被 pop 或 dismiss 之后是不會(huì)被釋放的琼掠。然而,由于 View 相關(guān)的對(duì)象一般都占用了較多了內(nèi)存停撞,這樣的設(shè)計(jì)通常來(lái)說(shuō)不是好的設(shè)計(jì)瓷蛙。如果開(kāi)發(fā)者由于性能問(wèn)題等原因而不得不這樣設(shè)計(jì)的時(shí)候,開(kāi)發(fā)者可以在報(bào)泄漏的類(lèi)里重載 - (BOOL)willDealloc 方法戈毒,直接 return NO; 以消除內(nèi)存泄漏的警告艰猬,這個(gè)消除內(nèi)存泄漏警告的方法與 MLeaksFinder 0.1 版本一致。
- 釋放不及時(shí)的 View 或 ViewController
例如埋市,發(fā)起網(wǎng)絡(luò)請(qǐng)求的時(shí)候冠桃,在網(wǎng)絡(luò)請(qǐng)求回調(diào)的 block 里強(qiáng)引用 ViewController,以便在網(wǎng)絡(luò)請(qǐng)求回來(lái)的時(shí)候刷新界面道宅。在網(wǎng)絡(luò)請(qǐng)求比較慢的情況下食听,這種做法存在兩個(gè)問(wèn)題:
ViewController 被 pop 之后,由于被 block 強(qiáng)引用導(dǎo)致釋放不及時(shí)
ViewController 被 pop 之后污茵,如果網(wǎng)絡(luò)請(qǐng)求回來(lái)了樱报,不應(yīng)該繼續(xù)做刷新界面的事,浪費(fèi) CPU
所以泞当,對(duì)于這種情況迹蛤,我們應(yīng)該在 block 里弱引用 ViewController,而不是強(qiáng)引用。
下面我們來(lái)看如何利用對(duì)象的生命周期來(lái)分析內(nèi)存的真正使用情況盗飒,分三種情況:
- 單例或者被 cache 起來(lái)復(fù)用
如下面所示嚷量,在第一次 pop 的時(shí)候報(bào) Memory Leak,在之后的重復(fù) push 并 pop 同一個(gè) ViewController 過(guò)程中逆趣,即不報(bào) Object Deallocated蝶溶,也不報(bào) Memory Leak。這種情況下我們可以確定該對(duì)象被設(shè)計(jì)成單例或者 cache 起來(lái)了汗贫。
pop push pop push pop
----------> Leak ----------> | ----------> | ----------> | ---------->
- 釋放不及時(shí)
如下面所示身坐,在第一次 pop 的時(shí)候報(bào) Memory Leak,在之后的重復(fù) push 并 pop 同一個(gè) ViewController 過(guò)程中落包,對(duì)于同一個(gè)類(lèi)不斷地報(bào) Object Deallocated 和 Memory Leak部蛇。這種情況屬于釋放不及時(shí)的情況。
pop push pop push pop
----------> Leak ----------> Dealloc ----------> Leak ----------> Dealloc ----------> Leak
- 真正的內(nèi)存泄漏
如下面所示咐蝇,在第一次 pop 的時(shí)候報(bào) Memory Leak涯鲁,在之后的重復(fù) push 并 pop 同一個(gè) ViewController 過(guò)程中,不報(bào) Object Deallocated有序,但每次 pop 之后又報(bào) Memory Leak抹腿。這種情況下每回進(jìn)入并退出一個(gè)頁(yè)面后,就報(bào)有新的內(nèi)存泄漏旭寿,同時(shí)被報(bào)泄漏的對(duì)象又從來(lái)沒(méi)有釋放過(guò)警绩,可以確定是真正的內(nèi)存泄漏。
pop push pop push pop
----------> Leak ----------> | ----------> Leak ----------> | ----------> Leak
查找循環(huán)引用鏈
Facebook 在前陣子開(kāi)源了一個(gè)循環(huán)引用檢測(cè)工具 FBRetainCycleDetector 盅称。當(dāng)傳入內(nèi)存中的任意一個(gè) OC 對(duì)象肩祥,F(xiàn)BRetainCycleDetector 會(huì)遞歸遍歷該對(duì)象的所有強(qiáng)引用的對(duì)象,以檢測(cè)以該對(duì)象為根結(jié)點(diǎn)的強(qiáng)引用樹(shù)有沒(méi)有循環(huán)引用缩膝。
我們知道混狠,很多循環(huán)引用是 block 的使用不當(dāng)造成的。而 FBRetainCycleDetector 最大的技術(shù)亮點(diǎn)疾层,正在于如何找出一個(gè) block 的所有強(qiáng)引用對(duì)象将饺。對(duì)于這個(gè)感興趣的,可以看 facebook 的這篇 文章 痛黎。
然而予弧,F(xiàn)BRetainCycleDetector 的使用存在兩個(gè)問(wèn)題:
需要找到候選的檢測(cè)對(duì)象
檢測(cè)循環(huán)引用比較耗時(shí)
正是由于這兩個(gè)問(wèn)題,F(xiàn)BRetainCycleDetector 通常是結(jié)合其它工具一起使用湖饱,通過(guò)其它工具先找出候選的檢測(cè)對(duì)象桌肴,然后進(jìn)行有選擇的檢測(cè)。當(dāng) MLeaksFinder 與 FBRetainCycleDetector 結(jié)合使用時(shí)琉历,正好能達(dá)到很好的效果坠七。我們先通過(guò) MLeaksFinder 找到內(nèi)存泄漏的對(duì)象水醋,然后再過(guò) FBRetainCycleDetector 檢測(cè)該對(duì)象有沒(méi)有循環(huán)引用即可。
循環(huán)引用的輸出信息如下:
(
"-> MyTableViewCell ",
"-> _callback -> NSMallocBlock "
)
上面的信息表示彪置, MyTableViewCell 有一個(gè)強(qiáng)引用的成員變量 _callback 拄踪,該變量的類(lèi)型是 NSMallocBlock ,在 _callback 里拳魁,又強(qiáng)引用了 MyTableViewCell 造成循環(huán)引用惶桐。