更多內(nèi)容請挪步我的博客
前言
最近為[伯樂在線]翻譯一篇文章(該文章尚未發(fā)布)流济,作者 Russ Bishop 談到自己查找內(nèi)存泄漏的一次痛苦經(jīng)歷,讀后很有感觸洁奈,但是那些對于 Instruments 使用不太熟練的讀者可能由于 Bishop 忽略了一些細(xì)節(jié)奈虾,看后估計(jì)會覺得喝白水般不能解渴仅孩,所以寫一篇自己的經(jīng)歷將 Bishop 忽略的細(xì)節(jié)補(bǔ)全作為“譯后感”吧。
一種麻煩的檢查內(nèi)存泄漏方式
為什么麻煩還要介紹呢碧磅,因?yàn)榭吹接腥诉@樣調(diào)試的碘箍。這種方式需要寫些代碼,這些代碼完全是調(diào)試用的鲸郊,而且只能讓你知道是否存在泄漏丰榴,但是無法定位,看看這種方式如何操作秆撮。
在 dealloc 方法中寫一個日志四濒,如果退出頁面的時候能夠打印出日志內(nèi)容,說明該頁面沒有問題职辨,如果沒有打印盗蟆,那么就是有問題的。
- (void)dealloc {
DLog(@"release");
}
這樣如果頁面中有輸出 release拨匆,說明該頁面 CollageViewController 是沒有內(nèi)存泄漏的
2016-04-01 12:51:56.992 [267:19048] -[CollageViewController dealloc] - [Line 88] -- release
但是如果故意把循環(huán)引用引入姆涩,這個時候會有 Warning 警告,同時 release 也不會打印了
self.paperSizeView.paperSizeBlock = ^(PaperSizeModel *sizeModel) {
self.paperSize = sizeModel.paperSize;
}
要把 self 改為 WeakSelf惭每。
上面的 DLog 是我常用的一個日志宏骨饿,調(diào)試的時候能輸出函數(shù)名稱和所在行數(shù),定義如下
#ifdef DEBUG
#define DLog(args, ...) NSLog((@"%s - [Line %d] -- " args), __FUNCTION__, __LINE__, ##__VA_ARGS__);
#else
# define DLog(...)
#endif
如果覺得這樣的方式輸入的內(nèi)容還不夠豐富台腥,建議使用 喵神的代碼
#define NSLog(format, ...) do { \
fprintf(stderr, "<%s : %d> %s\n", \
[[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], \
__LINE__, __func__); \
(NSLog)((format), ##__VA_ARGS__); \
fprintf(stderr, "-------\n"); \
} while (0)
注意宏赘,這種方式在某些情況下仍然能執(zhí)行 dealloc,并不是十分可靠黎侈,還要確認(rèn) Instruments 的 Leaks 下沒有報(bào)錯才可以察署,例如以下這種比較常見的循環(huán)引用,需要使用 Leaks 查看峻汉。
常見的循環(huán)引用
@interface RetainCycleModel : NSObject
@property (strong, nonatomic) id obj;
@end
RetainCycleModel *model1 = [[RetainCycleModel alloc] init];
RetainCycleModel *model2 = [[RetainCycleModel alloc] init];
[model1 setObj:model2];
[model2 setObj:model1];
上例中 model1 強(qiáng)引用了 model2贴汪,model2 又強(qiáng)引用了 model1脐往。Demo 代碼點(diǎn)擊這里
如何用 Instrument 定位隱蔽的循環(huán)引用
還是拿 [LEAF Photo] 舉例,運(yùn)行 Allocation扳埂。
運(yùn)行后业簿,將所有的頁面都跑一遍,會看到內(nèi)存一直增長阳懂,當(dāng)返回頁面后內(nèi)存應(yīng)該是下降趨勢的梅尤。如果內(nèi)存沒有返回到初始狀態(tài),說明在某個頁面是有內(nèi)存泄漏的岩调。這時可以在搜索框搜索自己寫的類名巷燥。
看,這是進(jìn)入 CollageViewController 后退出后可以發(fā)現(xiàn)內(nèi)存不斷增長号枕,在內(nèi)存中搜索類名能看到這個視圖控制器沒有被釋放缰揪,還有一些其他相關(guān)的類。這樣就能知道是否有泄漏存在了堕澄。當(dāng)然了邀跃,這個代碼很容易查到原因,只需要點(diǎn)擊進(jìn)入每行對應(yīng)的代碼查看蛙紫,查看到 CollagePaperSizeView 時會指引到 paperSizeView 的 Block 中有循環(huán)引用。
Mark Generation
如果問題這么容易就定位到了途戒,Bishop 也就不用寫一大篇文章了坑傅,上面是剛好我們能搜到類名,正好出現(xiàn)問題的地方用了類名喷斋。如果搜索不出來應(yīng)當(dāng)怎么辦呢唁毒,那么 Mark Generation 該上場了。
就像 Bishop 所說的一樣星爪,按照重現(xiàn)步驟操作浆西,在出現(xiàn)問題的頁面進(jìn)入之前 Mark 一下,進(jìn)入頁面退出后再 Mark 一下顽腾。
這樣會生成 Generation A 和 Generation B近零,展開 Generation B ,看 Persistent 數(shù)最多的抄肖,這里是 non-object久信,再展開估計(jì)也看不出來什么了,點(diǎn)擊進(jìn)入 non-object漓摩,查看詳細(xì)裙士,接下來的頁面也是很多數(shù)據(jù),可以將 Responsible Libaray 列排序管毙,然后找到你的項(xiàng)目名字腿椎。
這里就沒有那么多噪聲了桌硫,很容易就能找到哪個是出問題的地方,看這里提示 save 方法有問題啃炸,就是該方法中出現(xiàn)了循環(huán)引用了鞍泉。
緩存
之后我想起來:這就是我們通常所謂的 Xcode,我打開終端肮帐,然后執(zhí)行 fuxcode (我寫的一個清理所有 DerivedData 的腳本)咖驮。又一次的重新編譯之后 Instruments 確認(rèn)沒有任何泄漏了。
Xcode 有緩存估計(jì)很多人都經(jīng)歷過這個痛楚吧训枢。寫個腳本把所有的 DerivedData 刪除 (哦托修,刪吧,刪了能把硬盤騰出很多地方)恒界,或者只刪除當(dāng)前項(xiàng)目的 DerivedData 吧睦刃,點(diǎn)擊菜單 Xcode -> Product,按住 Option 鍵十酣,Clean 變成 Clean Build Folder涩拙,清理下編譯目錄即可。