【YFMemoryLeakDetector】人人都能理解的 iOS 內(nèi)存泄露檢測工具類
背景
即使到今天,iOS 應用的內(nèi)存泄露檢測,仍然是一個很重要的主題。我在一年前,項目中隨手寫過一個簡單的工具類,當時的確解決了大問題。視圖和控制器相關的內(nèi)存泄露,幾乎都不存在了。后來想著一直就那個工具,寫一篇文章,不過一直沒有寫。
時過境遷,今天在網(wǎng)上搜了下 “iOS 內(nèi)存泄露檢測”,各種討論技術(shù)文章,有點頭大。我忍不住看了下自己當時的代碼,突然感覺自己的思路好特別,好有創(chuàng)意。我真的就是在“創(chuàng)建”時把數(shù)據(jù)記錄到一個字典里,在“釋放”時,從字典里移出對象;所謂的檢測,其實就是打印那個字典,仍然在字典中的很有可能就是泄露嘍。
當然,還是有一些技術(shù)細節(jié)的。我把舊代碼適度拆分整理為一個開源庫了,取名為 YFMemoryLeakDetector。本篇,將著重講述簡潔之下,可能不易察覺的一些考量。
注意:這個庫,相當程度上是為當時的項目量身定制的,你可能需要適當修改,才能在自己的項目中真正發(fā)揮出它的力量。
核心技術(shù)分析
AOP 機制,借助 Aspects 庫實現(xiàn)
Aspects 這個庫的基本用法,我專門說過,大家可以參考 Aspects– iOS的AOP面向切面編程的庫。當然,用黑魔法直接操作運行時,也是很酷的。不過我當時的確是因為偷懶,才用的 Aspects。一直到現(xiàn)在,我依然覺得,它可能比黑魔法更可靠些。
在字典中直接存儲指針地址,而不是直接存儲對象自身
存儲指針地址的好處是,就是不會因為存儲本身影響對象的引用計數(shù)。當然,指針地址本身,在 OC 中,其實就是對象自身。而要想得到存地址,不存對象的效果,就要祭出整個工具庫的靈魂函數(shù):
將對象轉(zhuǎn)換為 NSValue,直接以 NSValue 為鍵,來標記對象。這句代碼,是整個機制的靈魂所在,也是比其他類似的內(nèi)存泄露分析庫更簡潔的重要原因之一。我當時也是搜遍的整個網(wǎng)絡,才知道自己要的究竟是什么。
另外,還有一點必須提一下, NSValue 是可以在反向轉(zhuǎn)換為 oc 對象的,這有利于你在拿到工具庫提供的泄露信息后,進一步定位和分析問題:
對控制器和視圖,采用不同的攔截策略
- 對象銷毀,統(tǒng)一攔截的是 dealloc?,F(xiàn)在網(wǎng)上的很多策略,基本也是這樣。
- 對象創(chuàng)建,對于視圖,攔截的是 willMoveToSuperview: ;對于控制器攔截的是 viewDidLoad 。直到現(xiàn)在,我依然以為,沒有調(diào)用過這兩個方法的視圖或控制器對象,本身沒有多大的攔截價值。當然,這依然因項目而異。作為一個工具類,只要它能解決大多數(shù)場景下的問題,我覺得就可以了。
在 load 時,自動開啟監(jiān)測
所以,你只要把工具庫源碼拖拽到項目中,不需要任何修改,就可以自動監(jiān)測內(nèi)存泄露情況了。然后在需要的地方,在合適的時候,去讀取 YFMemoryLeakDetector 的單例屬性,分析結(jié)果即可。當然,這是我今天重構(gòu)優(yōu)化過的版本。原來是需要手動初始化的,好 Low,當時寫的!
“見碼如晤”
YFMemoryLeakDetector.h 頭文件部分,主要簡化為暴露了存儲可能有內(nèi)存泄露情況的視圖和控制器的字典屬性;同時提供了一個單例方法,以便于具體分析和操作內(nèi)存分析情況。
YFMemoryLeakDetector.m 實現(xiàn),借助于 Aspects 和 valueWithPointer: 代碼大大簡化。
使用示例:
這里展示一個基于工具類,二次分析的示例: