非常精準 iOS 內(nèi)存泄露檢測工具

出自:WeRead團隊博客 歡迎關注!

平常我們都會用 Instrument 的 Leaks / Allocations 或其他一些開源庫進行內(nèi)存泄露的排查,但它們都存在各種問題和不便,我們逐個來看這些工具的使用和存在的問題。

看看圖.png

Leaks

先看看 Leaks,從蘋果的開發(fā)者文檔里可以看到,一個 app 的內(nèi)存分三類:
Leaked memory: Memory unreferenced by your application that cannot be used again or freed (also detectable by using the Leaks instrument).

Abandoned memory: Memory still referenced by your application that has no useful purpose.

Cached memory: Memory still referenced by your application that might be used again for better performance.

  • 1、 Leaked memory 和 Abandoned memory 都屬于應該釋放而沒釋放的內(nèi)存,都是【內(nèi)存泄露】。
  • 2、而 Leaks 工具只負責檢測 Leaked memory,而不管 Abandoned memory。在 MRC 時代 Leaked memory 很常見,因為很容易忘了調(diào)用 release,但在 ARC 時代更常見的內(nèi)存泄露是循環(huán)引用導致的 Abandoned memory,Leaks 工具查不出這類內(nèi)存泄露,應用有限。

Allocation

對于 Abandoned memory,可以用 Instrument 的 Allocations 檢測出來。檢測方法是用 Mark Generation 的方式,當你每次點擊 Mark Generation 時,Allocations 會生成當前 App 的內(nèi)存快照,而且 Allocations 會記錄從上回內(nèi)存快照到這次內(nèi)存快照這個時間段內(nèi),新分配的內(nèi)存信息。舉一個最簡單的例子:
我們可以不斷重復 push 和 pop 同一個 UIViewController,理論上來說,push 之前跟 pop 之后,app 會回到相同的狀態(tài)。因此,在 push 過程中新分配的內(nèi)存,在 pop 之后應該被 dealloc 掉,除了前幾次 push 可能有預熱數(shù)據(jù)和 cache 數(shù)據(jù)的情況。如果在數(shù)次 push 跟 pop 之后,內(nèi)存還不斷增長,則有內(nèi)存泄露。因此,我們在每回 push 之前跟 pop 之后,都 Mark Generation 一下,以此觀察內(nèi)存是不是無限制增長。這個方法在 WWDC 的視頻里:Session 311 - Advanced Memory Analysis with Instruments,以及蘋果的開發(fā)者文檔:Finding Abandoned Memory 里有介紹。

用這種方法來發(fā)現(xiàn)內(nèi)存泄露還是很不方便的:
  • 首先,你得打開 Allocations
  • 其次,你得一個個場景去重復的操作
    無法及時得知泄露,得專門做一遍上述操作,十分繁瑣

開源庫

  • 1、幸運的是在 GitHub 上有一些內(nèi)存泄露檢測相關的項目,例如 HeapInspector-for-iOSMSLeakHunter。
    HeapInspector-for-iOS 可以說是 Allocations 的改進。它通過 hook 掉 alloc,dealloc,retain,release 等方法,來記錄對象的生命周期。具體的檢測內(nèi)存泄露的方法和原理,與 Instrument 的 Allocations 一致。然而它跟 Allocations 一樣,存在的問題是,你需要一個個場景去重復的操作,還有檢測不及時。
  • 2、MSLeakHunter 就簡單得多,它只檢測 UIViewController 和 UIView,通過 hook 掉 UIViewController 的 -viewDidDisappear:
    方法,并認為 -viewDidDisappear:
    后,UIViewController 將很快被釋放,如果 UIViewController 沒有被釋放,則打個建議日志。這種做法其實不是很好,-viewDidDisappear:
    被調(diào)用可能是因為又 push 進來一個新的 ViewController,把當前的 ViewController 擋住了,所以可能有很多錯誤的建議,需要結(jié)合你實際的操作去具體地分析日志。

MLeaksFinder 框架

MLeaksFinder 提供了內(nèi)存泄露檢測更好的解決方案。
1、只需要引入 MLeaksFinder,就可以自動在 App 運行過程檢測到內(nèi)存泄露的對象并立即提醒,
2、無需打開額外的工具。
3、也無需為了檢測內(nèi)存泄露而一個個場景去重復地操作。

總結(jié):MLeaksFinder 目前能自動檢測 UIViewController 和 UIView 對象的內(nèi)存泄露,而且也可以擴展以檢測其它類型的對象。

MLeaksFinder 的使用很簡單

參照 https://github.com/Zepo/MLeaksFinder,基本上就是把 MLeaksFinder 目錄下的文件添加到你的項目中,就可以在運行時(debug 模式下)幫助你檢測項目里的內(nèi)存泄露了,無需修改任何業(yè)務邏輯代碼,而且只在 debug 下開啟,完全不影響你的 release 包。

當發(fā)生內(nèi)存泄露時,MLeaksFinder 會中斷言,并準確的告訴你哪個對象泄露了。這里設計為中斷言而不是打日志讓程序繼續(xù)跑,是因為很多人不會去看日志,斷言則能強制開發(fā)者注意到并去修改,而不是犯拖延癥。

中斷言時,控制臺會有如下提示,View-ViewController stack 從上往下看,該 stack 告訴你,MyTableViewController 的 UITableView 的 subview UITableViewWrapperView 的 subview MyTableViewCell 沒被釋放。而且,這里我們可以肯定的是 MyTableViewController,UITableView,UITableViewWrapperView 這三個已經(jīng)成功釋放了。

 *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', 
reason: 'Possibly Memory Leak.In case that MyTableViewCell should not be dealloced, 
override -willDealloc in MyTableViewCell by returning NO.
View-ViewController stack: (
     MyTableViewController, 
     UITableView, 
     UITableViewWrapperView,
     MyTableViewCell
  )'

從 MLeaksFinder 的使用方法可以看出,MLeaksFinder 具備以下優(yōu)點:

1、使用簡單,不侵入業(yè)務邏輯代碼。
2、不用打開 Instrument不需要額外的操作,你只需開發(fā)你的業(yè)務邏輯,在你運行調(diào)試時就能幫你檢測內(nèi)存泄露發(fā)現(xiàn)及時,更改完代碼后一運行即能發(fā)現(xiàn)(這點很重要,你馬上就能意識到哪里寫錯了)精準,能準確地告訴你哪個對象沒被釋放

常見問題

在這里,有幾個問題需要解決:
1、不入侵開發(fā)代碼
這里使用了 AOP 技術(shù),hook 掉 UIViewController 和 UINavigationController 的 pop 跟 dismiss 方法,關于如何 hook,請參考 Method Swizzling。

2、遍歷相關對象
在實際項目中,我們發(fā)現(xiàn)有時候一個 UIViewController 被釋放了,但它的 view 沒被釋放,或者一個 UIView 被釋放了,但它的某個 subview 沒被釋放。這種內(nèi)存泄露的情況很常見,因此,我們有必要遍歷基于 UIViewController 的整棵 View-ViewController 樹。我們通過 UIViewController 的 presentedViewController 和 view 屬性,UIView 的 subviews 屬性等遞歸遍歷。對于某些 ViewController,如 UINavigationController,UISplitViewController 等,我們還需要遍歷 viewControllers 屬性。

3、構(gòu)建堆棧信息
需要構(gòu)建 View-ViewController stack 信息以告訴開發(fā)者是哪個對象沒被釋放。在遞歸遍歷 View-ViewController 樹時,子節(jié)點的 stack 信息由父節(jié)點的 stack 信息加上子結(jié)點信息即可。

4、例外機制
對于有些 ViewController,在被 pop 或 dismiss 后,不會被釋放(比如單例),因此需要提供機制讓開發(fā)者指定哪個對象不會被釋放,這里可以通過重載上面的 -willDealloc
方法,直接 return NO 即可。

5、特殊情況
對于某些特殊情況,釋放的時機不大一樣(比如系統(tǒng)手勢返回時,在劃到一半時 hold 住,雖然已被 pop,但這時還不會被釋放,ViewController 要等到完全 disappear 后才釋放),需要做特殊處理,具體的特殊處理視具體情況而定。

6、系統(tǒng)View
某些系統(tǒng)的私有 View,不會被釋放(可能是系統(tǒng) bug 或者是系統(tǒng)出于某些原因故意這樣做的,這里就不去深究了),因此需要建立白名單

這個是我分享的另外一篇相關文章,可以參考: http://m.itdecent.cn/p/a2f3cbc4a440

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

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

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