本文列舉的不是查找 iOS 應用內存問題的必要流程,只是講述筆者在干這檔子事兒的時候充岛,可能會用到的手段而已。??
Clang Static Analyzer
在應用運行起來之前,我們就能通過 Xcode 集成的靜態(tài)分析工具 Clang Static Analyzer锋拖,分析下面幾種類型的代碼問題:
- Logic flaws: 讀取未初始化的變量、解引用空指針等等祸轮;
- Memory management flaws:內存泄露等兽埃;
- Dead store:沒被使用的變量;
- API usage flaws:返回值等不符合 API 的要求适袜;
靜態(tài)分析的結果通常是基于某種假設邏輯而推斷產生的柄错,所以是否要修改代碼這一決定不能完全交給 analyzer。諸如“如何忽略特定的 dead store”、“如何告訴 analyzer 循壞塊一定會進入”等常見問題售貌,在這個 FAQ 中都列了出來给猾。
更方便的是,使用 scan-build 能在命令行中分析颂跨,還能導出 html 方便查看分析結果:
scan-build -o DIR_TO_STORE_RESULTS xcodebuild -configuration Debug -workspace PATH_TO_YOUR_WORKSPACE -scheme YOUR_SCHEME_NAME
如果你的是 project 而不是 workspace 就寫 -project PATH_TO_YOUR_PROJECT
……好吧這就是 xcodebuild 的參數(shù)而已跟 scan-build 無關敢伸。
上圖僅僅是 Bug Summary,下面才是 Report恒削,比較私密就不截了池颈,內容和用 Xcode 分析一樣,給出具體邏輯的判定流程钓丰,指出錯誤躯砰。筆者覺得這一步可以添加到持續(xù)集成中,這樣每天早上都能看到前一天可能新增的代碼問題携丁。
Leaks
Leaked Memory 指的是丟失引用的內存琢歇。打開 Leaks,盡管操作你的業(yè)務邏輯就好梦鉴,Leaks Check 那一欄出現(xiàn)紅色交叉就是檢測到泄露的地方矿微,選中 Leaks Object,點擊右邊的 E 按鈕(Extended Detail)可以看到調用棧尚揣,幫助定位問題涌矢。
不過要注意的是,任何一個大型軟件都不可能是完美的快骗,所以 Cocoa Touch 本身也是可能發(fā)生泄漏的娜庇,所以對于那些調用棧沒有用戶代碼而且你絞盡腦汁都想不明白的泄漏,說不定可以無視它方篮。
但對于 Abandoned Memory 來說名秀,Leaks 就無能為力了。Abandoned Memory 是那些引用沒有丟失藕溅,但不再被使用的內存匕得,在 ARC 下常見于由循環(huán)引用導致的內存不能釋放。譬如說 Push 進入某個 ViewController 之后巾表,再 Pop 回去汁掠,假設這個 ViewController 與其實例變量有循環(huán)引用,那么它們將不會被釋放集币,Leaks 沒辦法檢測到這種內存泄露考阱,于是乎需要 Allocations 這個工具(通常會新建個模板把這兩個工具放在一起用,嗯還有 VM Tracker)鞠苟。
Allocations
在 Push 之前乞榨,使用 Allocations 對當前內存中的 Heap 和 VM Region 進行 Mark Generation秽之,記錄內存的使用狀態(tài),接著 Push 進一個 ViewController 后 Pop 回去吃既,再 Mark 一遍考榨,這樣重復業(yè)務邏輯好幾次,就能發(fā)現(xiàn)每次內存的增長情況鹦倚。前一兩次的快照可能有一些 lib 的緩存董虱,所以要特別留意之后的內存增長情況。
每次 Mark Generation 之后申鱼,Growth 還是會不斷變化的愤诱,這里筆者不清楚是系統(tǒng)延遲釋放內存,還是工具還來不及作對比捐友。我嘗試了下淫半,多點 Mark Generation 幾次或者手動觸發(fā) Memory Warning 可以讓前面的 Growth 快一點穩(wěn)定下來。
將 Track Display 改為 Allocation Density 可以留意到內存分配的動態(tài)匣砖,突然出現(xiàn)的尖峰就是在那段時間拼命地在分配內存科吭,稍微注意下是什么操作引起,說不定能優(yōu)化是吧猴鲫?畢竟堆上的內存分配可不是個輕松活对人。
我們還能再下面看到 Allocation Type 這個選項,Heap 和 VM Regions上的內存都是我們要關注的點拂共。我們實例化的對象牺弄、malloc 分配的內存都在 Heap 上,而 VM Regions 上是我們不能直接接觸的內存宜狐,但這不代表我們不用理會 VM Regions 任由其增長势告。比如 UIImage 對象在 Heap 上可能只有幾十字節(jié),但它代表的圖片在解壓縮后在 Image Region 上可能會占用幾十kb抚恒,甚至更大咱台。再比如,UIView 對象本身也不大俭驮,但是 CALayer backing stores 就不一定不起眼了回溺。
還有,回到 Record Settings混萝,我們常常需要在 Recorded Types 上設置忽略或追蹤特定類型的對象以便于我們分析(這里凸顯了給自己項目的類添加前綴的好處)遗遵,特別是當你已經將目標鎖定在某些類上時,這么做可以讓我們事半功倍譬圣。
Debug Memory Graph
這是 Xcode 8 的新功能瓮恭,就是點了 Debug Memory Graph 之后能看到當前在內存中的各種對象間引用關系雄坪。如果出現(xiàn)紫色框框的感嘆號厘熟,那么可能是有內存問題屯蹦,具體看上面的提示。有一點要提及的是绳姨,如果想看與某個對象的 backtrace登澜,要去 Edit-Scheme-Diagnostics 中把 Malloc Stack 的復選框給勾上,否則你可能找不這玩意兒是在哪里創(chuàng)建的……
第三方的檢測工具
除了官方的提供的工具之外飘庄,筆者還會使用一些的第三個工具脑蠕,最早使用過 HeapInspector-for-iOS 代替 Allocations,直接就在應用內分析 Heap 的增長情況跪削。還有 FBRetainCycleDetector谴仙,通過 DFS 尋找以對象為節(jié)點、強引用關系為邊的有向圖中的環(huán)路碾盐,從而達到找到循環(huán)引用的目的晃跺。上面的兩個庫都有些缺點,比如前者也是要不斷重復業(yè)務邏輯并推斷毫玖,后者要修改代碼才能查找環(huán)路掀虎。而 WeRead 團隊出品的 MLLeaksFinder 解決了這兩個問題,具體可以看他們博客的介紹《MLeaksFinder:精準 iOS 內存泄露檢測工具》以及《MLeaksFinder 新特性》付枫,這個工具在實踐中還是比較好用的烹玉。
最后
還是那句話,上面的所羅列不是查找 iOS 應用內存問題的必要流程阐滩,僅僅是常用的手段二打。另外,還有一點是調試的心態(tài)掂榔,當筆者要去查一個應用的內存問題時址儒,筆者會像個XX一樣,總是想找到內存問題衅疙,不找到不甘心??莲趣,然后會懷疑自己對項目代碼不夠了解、自己調試經驗不足以及工具使用的姿勢不正確等等饱溢。這顯然是一種心理疾病喧伞,望各位以此為鑒,將更多的精力投入到新需求的開發(fā)中绩郎,而不是深陷調試旋渦無法自拔潘鲫。
話說剛來公司一個月就給項目寫了兩個 bug,也是個挺悲傷的故事??肋杖。