內(nèi)存泄漏是很常見(jiàn)的問(wèn)題冒萄,雖然在ARC后蘋果為我們解決了大量的煩惱,但是一個(gè)不小心還是會(huì)陷進(jìn)去嘱能。這段時(shí)間在公司做了一些內(nèi)存泄漏排查與修改的工作擎浴,介紹一下用到的一些檢測(cè)方式和常見(jiàn)的場(chǎng)景。
內(nèi)存泄漏的檢測(cè)方式
1. 靜態(tài)檢測(cè)
使用XCode分析功能根暑,Product->Analyze
使用靜態(tài)檢測(cè)可以檢查出一些明顯的沒(méi)有釋放的內(nèi)存力试,包括NSObject和CF開(kāi)頭的內(nèi)存泄漏,最常見(jiàn)問(wèn)題有2種排嫌,這些問(wèn)題都不復(fù)雜畸裳,需要的是細(xì)心:
- MRC的文件,經(jīng)常遺漏release或者autorelease
- C方式申請(qǐng)的內(nèi)存淳地,忘記釋放了
static inline NSString* iphone_device_info(){
size_t size;
sysctlbyname("hw.machine", NULL, &size, NULL, 0);
char *machine = (char*)malloc(size);
sysctlbyname("hw.machine", machine, &size, NULL, 0);
NSString *platform = [NSString stringWithCString:machine encoding:NSASCIIStringEncoding];
...
}
if (alpha != 1) {
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
CGColorRef color = CGColorCreate(colorSpaceRef, (CGFloat[]){255, 255, 255, 0.3});
[btn.layer setBorderColor:color];
}
不過(guò)在修改的時(shí)候需要注意:
- 這些場(chǎng)景是否真的泄漏了怖糊,以免造成重復(fù)釋放。
- 注意該文件是MRC還是ARC颇象,需要不同的內(nèi)存處理方式伍伤。
- 如果是C申請(qǐng)的內(nèi)存,注意new delete遣钳, malloc free的配對(duì)處理扰魂。
比如我們的代碼中有這樣的問(wèn)題:
if ([self.itemOutput hasNewPixelBufferForItemTime:currentTime]) {
[self displayPixelBuffer:[self.itemOutput copyPixelBufferForItemTime:currentTime itemTimeForDisplay:NULL]];
[_program useGlProgram];
}
在進(jìn)行靜態(tài)檢測(cè)時(shí),會(huì)報(bào)“copyPixelBufferForItemTime”內(nèi)存泄漏蕴茴,copy后的內(nèi)存需要釋放劝评。可事實(shí)上倦淀,在“displayPixelBuffer”函數(shù)中已經(jīng)對(duì)傳入對(duì)內(nèi)存進(jìn)行了釋放蒋畜,
我們姑且不論這樣對(duì)寫法是否合理,只是切記在修改時(shí)注意結(jié)合上下文處理需要釋放的內(nèi)存
撞叽。
2. 動(dòng)態(tài)檢測(cè)
使用instruments百侧,這個(gè)工具網(wǎng)上的介紹比較多,可以參考如下
在Allocation中我們主要關(guān)注的是Persistent和Persistent Bytes,分別表示當(dāng)前時(shí)間段能扒,申請(qǐng)了但是還沒(méi)釋放的內(nèi)存數(shù)量和大小佣渴。
記住當(dāng)前這兩個(gè)值,然后進(jìn)入某個(gè)新頁(yè)面初斑,退出該頁(yè)面辛润,觀察這兩個(gè)值是否增加。需要注意的是,由于有些圖片調(diào)用本身是有緩存的砂竖,如果是用SDWebImage管理真椿,則網(wǎng)絡(luò)圖片都會(huì)緩存在內(nèi)存中。因此退出頁(yè)面后內(nèi)存有增加是正常的乎澄,而且還有些單例的內(nèi)存也是不會(huì)釋放的突硝,我們可以再次進(jìn)入同一個(gè)頁(yè)面,在圖片都加載過(guò)的情況下置济,反復(fù)進(jìn)入退出查看內(nèi)存狀況解恰,如果持續(xù)增加,則說(shuō)明有泄漏浙于。
3. 第三方工具MLeaksFinder
主要檢查UI方面的泄漏护盈,集成簡(jiǎn)單,在我看來(lái)主要的好處有3點(diǎn):
1.不同于instrument的分析功能羞酗,需要人工觀察腐宋,這個(gè)工具自動(dòng)檢測(cè),發(fā)現(xiàn)有泄漏后可以實(shí)時(shí)進(jìn)行提示檀轨,雖然主要是針對(duì)UI胸竞,但對(duì)于一般的工程來(lái)說(shuō),內(nèi)存泄漏的場(chǎng)景中還是以UI居多参萄,因此可以解決很大部分的問(wèn)題卫枝。
2.在新功能的開(kāi)發(fā)過(guò)程和解決bug的過(guò)程中,出現(xiàn)內(nèi)存泄漏都可以很輕松的檢測(cè)出來(lái)拧揽。
3.可以給測(cè)試同學(xué)打包時(shí)開(kāi)啟這個(gè)功能剃盾,幫助測(cè)試發(fā)現(xiàn)問(wèn)題腺占。
具體特點(diǎn)淤袜,原理和集成方式可以參考如下博客的內(nèi)容:
內(nèi)存泄漏的常見(jiàn)場(chǎng)景
CF類型內(nèi)存
注意以creat,copy
作為關(guān)鍵字的函數(shù)都是需要釋放內(nèi)存的,注意配對(duì)使用衰伯。比如:CGColorCreate<-->CGColorRelease
MRC內(nèi)存使用
這部分不做詳細(xì)介紹铡羡,也是注意配對(duì)使用,需要說(shuō)明的是意鲸,如果代碼中有部分文件是MRC的烦周,在已有文件中加代碼的時(shí)候注意一下,不能都按照ARC的方式處理怎顾。
ARC內(nèi)存使用
ARC已經(jīng)為我們做了很多封裝读慎,我們不必再顯示的調(diào)用retain,release槐雾,但是還是要注意循環(huán)引用的場(chǎng)景夭委。
在此,常規(guī)的對(duì)象間的互相引用和block的循環(huán)引用大家都比較了解募强,這里就不贅述了株灸,羅列幾個(gè)我們代碼中的錯(cuò)誤崇摄。
1. NSTimer
NSTimer會(huì)造成循環(huán)引用,timer會(huì)強(qiáng)引用target即self慌烧,一般self又會(huì)持有timer作為屬性逐抑,這樣就造成了循環(huán)引用。
那么屹蚊,如果timer只作為局部變量厕氨,不把timer作為屬性呢?同樣釋放不了淑翼,因?yàn)樵诩尤雛unloop的操作中腐巢,timer被強(qiáng)引用。而timer作為局部變量玄括,是無(wú)法執(zhí)行invalidate的冯丙,所以在timer被invalidate之前,self也就不會(huì)被釋放遭京。
所以我們要注意胃惜,不僅僅是把timer當(dāng)作實(shí)例變量的時(shí)候會(huì)造成循環(huán)引用,只要申請(qǐng)了timer哪雕,加入了runloop船殉,并且target是self,雖然不是循環(huán)引用斯嚎,但是self卻沒(méi)有釋放的時(shí)機(jī)利虫。如下方式申請(qǐng)的定時(shí)器,self已經(jīng)無(wú)法釋放了堡僻。
NSTimer *timer = [NSTimer timerWithTimeInterval:5 target:self selector:@selector(commentAnimation) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
解決這種問(wèn)題有幾個(gè)實(shí)現(xiàn)方式糠惫,大家可以根據(jù)具體場(chǎng)景去選擇:
- 增加startTimer和stopTimer方法,在合適的時(shí)機(jī)去調(diào)用钉疫,比如可以在viewDidDisappear時(shí)stopTimer硼讽,或者由這個(gè)類的調(diào)用者去設(shè)置。
- 每次任務(wù)結(jié)束時(shí)使用dispatch_after方法做延時(shí)操作牲阁。注意使用weakself固阁,否則也會(huì)強(qiáng)引用self。
- (void)startAnimation
{
WS(weakSelf);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[weakSelf commentAnimation];
});
}
- 使用GCD的定時(shí)器城菊,同樣注意使用weakself备燃。
WS(weakSelf);
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC, 1 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
[weakSelf commentAnimation];
});
dispatch_resume(timer);
我的另外一篇文章對(duì)各種解決方式和其他的定時(shí)器操作做了總結(jié)(http://www.reibang.com/p/ca579c502894),大家可以參考凌唬。
2. NSNotification
使用block的方式增加notification并齐,引用了self,在刪除notification之前,self不會(huì)被釋放冀膝,與timer的場(chǎng)景類似唁奢,其實(shí)這段代碼已經(jīng)聲明了weakself,但是調(diào)用_eventManger方法還是引起了循環(huán)引用窝剖。
也就是說(shuō)麻掸,即使我們沒(méi)有調(diào)用self方法,_xxx
也會(huì)造成循環(huán)引用赐纱。
[[NSNotificationCenter defaultCenter] addObserverForName:kUserSubscribeNotification object:nil queue:nil usingBlock:^(NSNotification *note) {
if (note) {
Model *model=(Model *)note.object;
if ([model.subId integerValue] == [weakSelf.subId integerValue]) {
[_eventManger playerSubsciption:NO];
}
}
}
3. __block
__block
在MRC
中是不會(huì)增加引用的脊奋,可是在ARC
中會(huì)增加,所以在ARC中疙描,只能使用__weak去打破循環(huán)引用诚隙。
__block MyViewController *weaksef = self;
_myViewController.editBlock = ^(){
[weaksef refreshUI];
};
另外聲明一點(diǎn),并非所有的block都需要使用weak來(lái)打破循環(huán)引用起胰,如果self沒(méi)有持有block就不會(huì)造成循環(huán)引用久又。而有些地方之所以使用了__weak,是為了在[self dealloc]之后就不再執(zhí)行了效五。在這種場(chǎng)景下使用weakself時(shí)地消,也需要注意,如果self被釋放了會(huì)不會(huì)引起異常畏妖。比如下面這段代碼把weakself取到的值作為入?yún)⒙鲋矗绻鹲elf釋放了,傳nil戒劫,引起crash:
[[HttpCore sharedInstance] getRequestWithPath:url target:self params:params success:^(NSDictionary *result, NSURLRequest *request, NSHTTPURLResponse *response) {
} failure:^(NSError *error) {
dispatch_group_leave(weakSelf.startGroup);
}];