動(dòng)態(tài)內(nèi)存泄露分析
時(shí)間:2017年9月29日 周五
1、內(nèi)存泄露
通常說的內(nèi)存泄漏是指堆內(nèi)存的泄漏运授。
堆內(nèi)存是指程序從堆中分配的烤惊、大小任意的(內(nèi)存塊的大小可以在程序運(yùn)行期決定)乔煞,使用完后必須顯式釋放的內(nèi)存。創(chuàng)建對(duì)象時(shí)撕氧,用alloc瘤缩、new、copy等方法從堆中分配到一塊內(nèi)存伦泥,使用完后剥啤,程序必須負(fù)責(zé)相應(yīng)的調(diào)用free或delete釋放該內(nèi)存塊,否則不脯,這塊內(nèi)存就不能被再次使用府怯,我們就說這塊內(nèi)存泄漏了。
如果程序運(yùn)行時(shí)一直分配內(nèi)存而不及時(shí)釋放無用的內(nèi)存防楷,程序占用的內(nèi)存越來越大牺丙,直到把系統(tǒng)分配給該APP的內(nèi)存消耗殫盡,程序因無內(nèi)存可用導(dǎo)致崩潰复局。
可能引起的問題:
1)內(nèi)存消耗殆盡的時(shí)候冲簿,程序會(huì)因沒有內(nèi)存被殺死,即crash亿昏;
2)當(dāng)內(nèi)存快要用完的時(shí)候峦剔,會(huì)非常的卡頓;
3)如果是ViewController沒有釋放掉角钩,引起的內(nèi)存泄露吝沫,還會(huì)引起其他很多問題,尤其是和通知相關(guān)的递礼。沒有被釋放掉的ViewController還能接收通知惨险,還會(huì)執(zhí)行相關(guān)的動(dòng)作,所以會(huì)引起各種各樣的異常情況的發(fā)生脊髓。
從iOS5開始apple采用ARC機(jī)制辫愉,不需要我們對(duì)內(nèi)存進(jìn)行管理了。但在實(shí)際開發(fā)過程中将硝,不可避免的出現(xiàn)內(nèi)存泄露的情況恭朗。
2、xcode自帶的工具
Instrument中的Leaks袋哼、Allocations工具可以幫助我們進(jìn)行內(nèi)存泄露的排查冀墨,但它們存在不足闸衫。
在 ARC 時(shí)代更常見的內(nèi)存泄露是循環(huán)引用導(dǎo)致的 Abandoned memory涛贯,Leaks 工具查不出這類內(nèi)存泄露,應(yīng)用有限蔚出;
Instrument 的 Allocations 檢測(cè)出 Abandoned memory弟翘,但是用起來不方便虫腋,需要一個(gè)一個(gè)地重現(xiàn)場(chǎng)景,且無法及時(shí)的知道泄露稀余,比較繁瑣悦冀。總之睛琳,一句話就是盒蟆,不好使。
3师骗、開源框架
在 GitHub 上有一些內(nèi)存泄露檢測(cè)相關(guān)的項(xiàng)目历等,例如 HeapInspector-for-iOS 和 MSLeakHunter。
1)HeapInspector-for-iOS
HeapInspector-for-iOS 可以說是 Allocations 的改進(jìn)辟癌。它通過 hook 掉 alloc寒屯,dealloc,retain黍少,release 等方法寡夹,來記錄對(duì)象的生命周期。具體的檢測(cè)內(nèi)存泄露的方法和原理厂置,與 Instrument 的 Allocations 一致菩掏。然而它跟 Allocations 一樣,存在的問題是农渊,你需要一個(gè)個(gè)場(chǎng)景去重復(fù)的操作患蹂,還有檢測(cè)不及時(shí)。
2)MSLeakHunter
MSLeakHunter 就簡(jiǎn)單得多砸紊,它只檢測(cè) UIViewController 和 UIView传于,通過 hook 掉 UIViewController 的 -viewDidDisappear: 方法,并認(rèn)為 -viewDidDisappear: 后醉顽,UIViewController 將很快被釋放沼溜,如果 UIViewController 沒有被釋放,則打個(gè)建議日志游添。這種做法其實(shí)不是很好系草,-viewDidDisappear: 被調(diào)用可能是因?yàn)橛?push 進(jìn)來一個(gè)新的 ViewController,把當(dāng)前的 ViewController 擋住了唆涝,所以可能有很多錯(cuò)誤的建議找都,需要結(jié)合你實(shí)際的操作去具體地分析日志。
4廊酣、動(dòng)態(tài)檢測(cè)工具:MLeaksFinder
1)MLeaksFinder檢測(cè)內(nèi)存泄露原理
MLeaksFinder用于檢測(cè)viewController能耻、view是否存在內(nèi)存泄露的情況。
當(dāng)一個(gè) UIViewController 被 pop 或 dismiss 后,該 UIViewController 包括它的 view晓猛,view 的 subviews 等等將很快被釋放(除非你把它設(shè)計(jì)成單例饿幅,或者持有它的強(qiáng)引用,但一般很少這樣做)戒职。于是栗恩,我們只需在一個(gè) ViewController 被 pop 或 dismiss 一小段時(shí)間后,看看該 UIViewController洪燥,它的 view磕秤,view 的 subviews 等等是否還存在。
具體的方法是捧韵,為基類 NSObject 添加一個(gè)方法 -willDealloc 方法亲澡,該方法的作用是,先用一個(gè)弱指針指向 self纫版,并在一小段時(shí)間(3秒)后床绪,通過這個(gè)弱指針調(diào)用 -assertNotDealloc,而 -assertNotDealloc 主要作用是直接中斷言其弊。
- (BOOL)willDealloc {
__weak id weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
__strong id strongSelf = weakSelf;
[strongSelf assertNotDealloc];
});
return YES;
}
- (void)assertNotDealloc {
if ([MLeakedObjectProxy isAnyObjectLeakedAtPtrs:[self parentPtrs]]) {
return;
}
[MLeakedObjectProxy addLeakedObject:self];
}
這樣癞己,當(dāng)我們認(rèn)為某個(gè)對(duì)象應(yīng)該要被釋放了,在釋放前調(diào)用這個(gè)方法梭伐,如果3秒后它被釋放成功痹雅,weakSelf 就指向 nil,不會(huì)調(diào)用到 -assertNotDealloc 方法糊识,也就不會(huì)中斷言绩社,如果它沒被釋放(泄露了),-assertNotDealloc 就會(huì)被調(diào)用中斷言赂苗。這樣愉耙,當(dāng)一個(gè) UIViewController 被 pop 或 dismiss 時(shí)(我們認(rèn)為它應(yīng)該要被釋放了),我們遍歷該 UIViewController 上的所有 view拌滋,依次調(diào) -willDealloc朴沿,若3秒后沒被釋放,就會(huì)中斷言败砂,并且調(diào)用[MLeakedObjectProxy addLeakedObject:self];該方法會(huì)獲取內(nèi)存泄露的信息赌渣,并彈框顯示。
2)特點(diǎn):
A昌犹、不入侵代碼
直接拖進(jìn)工程坚芜,配置設(shè)置即可使用。
使用了 AOP 技術(shù)斜姥,hook 掉 UIViewController 和 UINavigationController 的 pop 跟 dismiss 方法鸿竖。(AOP技術(shù)路操,可以閱讀”漫談iOS AOP編程之路“)
B、可以建立例外
對(duì)于有些 ViewController千贯,在被 pop 或 dismiss 后,不會(huì)被釋放(比如單例)搞坝,因此需要提供機(jī)制讓開發(fā)者指定哪個(gè)對(duì)象不會(huì)被釋放搔谴,這里可以通過重載上面的 -willDealloc 方法,直接 return NO 即可桩撮。
C敦第、手動(dòng)擴(kuò)展:MLCheck()
MLeaksFinder目前只檢測(cè) ViewController 跟 View 對(duì)象。為此店量,MLeaksFinder 提供了一個(gè)手動(dòng)擴(kuò)展的機(jī)制芜果,你可以從 UIViewController 跟 UIView 出發(fā),去檢測(cè)其它類型的對(duì)象的內(nèi)存泄露融师。如下所示右钾,我們可以檢測(cè) UIViewController 底下的 View Model:
- (BOOL)willDealloc {
if (![super willDealloc]) {
return NO;
}
MLCheck(self.viewModel);
return YES;
}
3)MLeaksFinder的增強(qiáng):FBRetainCycleDetector
MLeaksFinder做到的是:告訴我們,viewController旱爆、view是否存在內(nèi)存泄露舀射。
MLeaksFinder不能告訴我們,當(dāng)viewController怀伦、view發(fā)生內(nèi)存泄露時(shí)脆烟,是在什么地方出現(xiàn)的,不能提供內(nèi)存泄露的詳細(xì)信息房待。
因此邢羔,我們接入了一個(gè)檢測(cè)循環(huán)引用的框架FBRetainCycleDetector,它是Facebook的一個(gè)開源工具桑孩,專門用于檢測(cè)對(duì)象的引用情況拜鹤,并輸出存在引用循環(huán)中的各對(duì)象和引用。
當(dāng)MLeaksFinder檢測(cè)到內(nèi)存泄露時(shí)流椒,將存在內(nèi)存泄露的對(duì)象傳參給FBRetainCycleDetector署惯,F(xiàn)BRetainCycleDetector檢測(cè)該對(duì)象的引用情況,并彈框顯示檢測(cè)結(jié)果镣隶。
在MLeaksFinder.h文件中有個(gè)開關(guān)控制极谊,是否開啟FBRetainCycleDetector
#define MEMORY_LEAKS_FINDER_RETAIN_CYCLE_ENABLED 1
5、下面用一個(gè)例子來介紹它們的使用
1)創(chuàng)建GGMLViewController安岂,給它制造一個(gè)內(nèi)存泄露:
2)運(yùn)行工程
點(diǎn)擊測(cè)試工程中的”MLeaksFinder使用“
彈出新的ViewController轻猖,一直點(diǎn)擊按鈕
action按鈕即可,點(diǎn)到不能再?gòu)棾鲂碌腣iewController后域那,就點(diǎn)擊back咙边,回退到首頁(yè)猜煮。
等待3秒時(shí)間,彈出循環(huán)引用的提示框
點(diǎn)擊”定位“败许,彈出相關(guān)循環(huán)引用的信息王带,根據(jù)這信息,我們可以清楚的知道哪里出現(xiàn)了問題市殷。
Demo地址:https://github.com/lignpeng/testDemo
注意:在使用MLeaksFinder時(shí)愕撰,建議把All Exceptions斷點(diǎn)禁掉,避免出現(xiàn)些沒必要的中斷醋寝,這種中斷是在FBRetainCycleDetector框架出現(xiàn)的搞挣,但不影響使用。
引用文章:
1)MLeaksFinder:精準(zhǔn)iOS內(nèi)存泄露工具: http://wereadteam.github.io/2016/02/22/MLeaksFinder/
2)使用FBRetainCycleDetector檢測(cè)引用循環(huán):http://www.reibang.com/p/2c7a7c53c91a
3)漫談iOS AOP編程之路:http://www.reibang.com/p/addd4eac54ed
4)iOS內(nèi)存泄露個(gè)人經(jīng)驗(yàn):http://www.reibang.com/p/10254b4b19f3
5)iOS 測(cè)試工程Demo:http://www.reibang.com/p/4580a370a4d3