iOS內(nèi)存泄漏自動(dòng)檢測(cè)工具PLeakSniffer

新款objective-C內(nèi)存泄漏自動(dòng)檢測(cè)工具PLeakSnifferGitHub地址

背景

前些天讀到WeRead團(tuán)隊(duì)分享的一款內(nèi)存泄漏檢測(cè)工具MLeaksFinder,恍惚想起早些時(shí)候自己也有過編寫這樣一個(gè)小工具的想法,不知道由于什么原因把這事給忘記了。在仔細(xì)讀過MLeaksFinder源碼,了解實(shí)現(xiàn)思路之后,發(fā)現(xiàn)和自己最初的想法并不相同,終于在上個(gè)周末戰(zhàn)勝拖延癥將之前的想法付諸于代碼,也就誕生了這款功能類似的內(nèi)存泄漏檢測(cè)工具PLeakSniffer。建議讀者先詳細(xì)閱讀下MLeaksFinder這篇博客。

為什么要再造輪子

我在公司的項(xiàng)目里實(shí)際試用了MLeaksFinder,還查處了2處泄漏??。根據(jù)MLeaksFinder代碼文件中日期推測(cè),這個(gè)項(xiàng)目至少已開始半年有余,并在微信讀書上得到了實(shí)踐驗(yàn)證,在功能性和穩(wěn)定性上都應(yīng)該有不錯(cuò)的表現(xiàn)。

在編寫完P(guān)LeakSniffer之后,查出了與MLeaksFinder相同的內(nèi)存泄漏,思路迥異的代碼抵達(dá)了相同的終點(diǎn),寫代碼的樂趣莫過于此。新的思路或許還能拋磚引玉,如果激發(fā)更多的創(chuàng)意,也算是對(duì)iOS開發(fā)社區(qū)的一點(diǎn)小貢獻(xiàn)。

MLeaksFinder現(xiàn)階段能查處UIViewController和UIView的泄漏,我早先的想法還能遞歸的查出UIViewController之下所有Property的泄漏,并在PLeakSniffer及公司項(xiàng)目中得到了初步的驗(yàn)證,這算是對(duì)MLeaksFinder功能的一個(gè)小補(bǔ)充。

這類工具的意義

在我們討論這類工具的意義之前,我們先得明確一點(diǎn):

如果不使用Instrument當(dāng)中的Leak檢測(cè)工具,并沒有什么輕易的100%精準(zhǔn)的內(nèi)存泄漏檢測(cè)方式。

但這類工具還是有其存在價(jià)值的,內(nèi)存泄漏的危害不用贅述,如果有一款工具能在80%的場(chǎng)景下檢測(cè)出可能的內(nèi)存泄漏,而且這種檢測(cè)并不會(huì)帶來任何副作用(不影響生產(chǎn)環(huán)境代碼),為什么不使用它呢。

大部分人都低估了他們寫代碼時(shí)導(dǎo)致意外內(nèi)存泄漏的可能性。Retain Cycle,Block強(qiáng)引用,NSTimer釋放不當(dāng),這些常見的錯(cuò)誤還是很容易出現(xiàn)在我們的代碼里,Instrument每使用一次要費(fèi)些精力,適合做定期的大排查。平常時(shí)候就更適合用MLeaksFinder,PLeakSniffer這類工具來做實(shí)時(shí)監(jiān)控,提供免費(fèi)建議。

PLeakSniffer實(shí)現(xiàn)思路

我們絕大部分時(shí)候都是在編寫UIViewController,UIViewController就像一個(gè)根節(jié)點(diǎn),持有并管理著很多的子節(jié)點(diǎn)對(duì)象,這些子節(jié)點(diǎn)的生命周期都依賴于Controller,Controller釋放的時(shí)候,他們也隨之釋放。用一張圖簡(jiǎn)單的描述他們的關(guān)系:

根據(jù)各個(gè)應(yīng)用使用的設(shè)計(jì)模式不同(MVC,MVP,MVVM等),Controller所持有的Property也不相同。這里我們使用MVP作為例子,Controller所包含的對(duì)象就包括各種View對(duì)象,和Presenter,Model對(duì)象。當(dāng)然每個(gè)對(duì)象又有可能持有更多的子對(duì)象。

PLeakSniffer基于這樣一個(gè)假設(shè): > 如果Controller被釋放了,但其曾經(jīng)持有過的子對(duì)象如果還存在,那么這些子對(duì)象就是泄漏的可疑目標(biāo)。

當(dāng)然這個(gè)假設(shè)并不是一個(gè)100%適用的真理,不同工程師編寫代碼的方式風(fēng)格差別很大,有些會(huì)把某些UIViewController做成單例(個(gè)人覺得這不是個(gè)好主意。。),有些會(huì)把某些View緩存起來(即使Controller已被釋放),還會(huì)有其他考慮不到的場(chǎng)景。但在80%以上的場(chǎng)景,我們?cè)贑ontroller結(jié)束生命周期之后會(huì)將其持有的資源一并釋放。這時(shí)候PLeakSniffer可以發(fā)揮用處,給你一些免費(fèi)的泄漏建議。

那么怎么在Controller被釋放之后,知道其持有的對(duì)象沒有被釋放呢?

一個(gè)小技巧可以達(dá)成這個(gè)目標(biāo):子對(duì)象(比如view)建立一個(gè)對(duì)controller的weak引用,如果Controller被釋放,這個(gè)weak引用也隨之置為nil。那怎么知道子對(duì)象沒有被釋放呢?用一個(gè)單例對(duì)象每個(gè)一小段時(shí)間發(fā)出一個(gè)ping通知去ping這個(gè)子對(duì)象,如果子對(duì)象還活著就會(huì)一個(gè)pong通知。所以結(jié)論就是:如果子對(duì)象的controller已不存在,但還能響應(yīng)這個(gè)ping通知,那么這個(gè)對(duì)象就是可疑的泄漏對(duì)象。完整的結(jié)構(gòu)可以用下圖表示:

通知移除需要一個(gè)時(shí)機(jī),這里我們使用Associated Object機(jī)制給每一個(gè)子對(duì)象再生成一個(gè)Proxy對(duì)象,在Proxy對(duì)象的dealloc里面移除通知。

當(dāng)然什么時(shí)候去判斷一個(gè)對(duì)象的生命周期開始,什么時(shí)候判斷為結(jié)束,需要一個(gè)精挑細(xì)選的機(jī)制。View,Controller,Property各不相同。

PLeakSniffer采取保守的策略,通過Objective C的runtime機(jī)制,遞歸的將一個(gè)Controller所有強(qiáng)引用的property找出,并安裝proxy監(jiān)聽Ping通知。在我的測(cè)試下,基本上能將property泄漏的場(chǎng)景找出。

PLeakSniffer的使用方式很簡(jiǎn)答,通過Pod安裝后,通過以下代碼激活即可。

#if MY_DEBUG_ENV

[[PLeakSniffer sharedInstance] installLeakSniffer];

[[PLeakSniffer sharedInstance] addIgnoreList:@[@"MySingletonController"]];

#endif

addIgnoreList可以添加一些特殊的忽略名單,比如單例這種無法正確預(yù)測(cè)泄漏的對(duì)象。切記用Debug的宏將上述代碼包住,不要把這些檢測(cè)泄漏的代碼帶進(jìn)線上環(huán)境。

如果檢測(cè)到可疑泄漏,PLeakSniffer會(huì)在控制臺(tái)打印一條日志:

Controller泄漏:Detect Possible Controller Leak: %@

其他對(duì)象泄漏:Detect Possible Leak: %@

更多的細(xì)節(jié)請(qǐng)查閱代碼:GitHub地址。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容