現(xiàn)在iOS開發(fā)已經(jīng)是ARC甚至是swift的時代,但是內(nèi)存管理仍是一個重點關(guān)注的問題,如果只知盲目開發(fā)而不知個中原理,踩坑就跳不出來了,理解好內(nèi)存管理,能讓我們寫出更有質(zhì)量的代碼。
內(nèi)存管理是程序設(shè)計中很重要的一部分,程序在運行的過程中消耗內(nèi)存,運行結(jié)束后釋放占用的內(nèi)存。如果程序運行時一直分配內(nèi)存而不及時釋放無用的內(nèi)存,會造成這樣的后果:程序占用的內(nèi)存越來越大,直至內(nèi)存消耗殫盡,程序因無內(nèi)存可用導致崩潰,這樣的情況我們稱之為內(nèi)存泄漏。
ObjC的內(nèi)存管理比較簡潔,然而要深刻理解也不是一件易事,本文將介紹如何使用ObjC進行內(nèi)存管理。
1. 引用計數(shù)
在ObjC中,對象什么時候會釋放(或者對象占用的內(nèi)存什么時候會被回收利用)?
答案:當對象沒有被任何變量引用(也可以說是沒有指針指向該對象)的時候,就會被釋放。
那怎么知道對象已經(jīng)沒有被引用了呢?
ObjC采用引用計數(shù)(reference counting)的技術(shù)進行管理:
- 每個對象都有一個關(guān)聯(lián)的整數(shù),稱為引用計數(shù)器
- 當代碼需要使用該對象時,則將對象的引用技術(shù)加1
- 當代碼結(jié)束使用該對象時,則將對象的引用技術(shù)減1
- 當引用計數(shù)的值變?yōu)?時,表示對象沒有被任何代碼使用,此時對象將被釋放
與之對應的消息發(fā)送方法如下:
- 當對象被創(chuàng)建(通過
alloc、new或copy等方法)時,其引用計數(shù)初始值為1 - 給對象發(fā)送
retain消息,其引用計數(shù)加1 - 當對象引用計數(shù)歸0時,ObjC給對象發(fā)送
dealloc消息銷毀對象
下面通過一個簡單的例子來說明:
場景:有一個寵物中心(內(nèi)存):可以派出小動物(對象)陪小朋友們玩耍(對象引用者),現(xiàn)在xiaoming想和小狗一起玩耍。
新建Dog類,重寫其創(chuàng)建和銷毀的方法:
//
// Dog.m
// TextARC
//
// Created by taobaichi on 2017/3/27.
// Copyright ? 2017年 MaChao. All rights reserved.
//
#import "Dog.h"
@implementation Dog
-(instancetype)init{
if (self = [super init]) {
NSLog(@"小狗被派出去啦");
}
return self;
}
-(void)dealloc{
NSLog(@"小狗回到寵物中心");
[super dealloc];
}
@end
在main方法中創(chuàng)建dog對象,給dog發(fā)送消息
//
// main.m
// TextARC
//
// Created by taobaichi on 2017/3/27.
// Copyright ? 2017年 MaChao. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "Dog.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Dog * dog = [[Dog alloc]init];
//模擬:xiaoming需要和小狗玩耍,需要將其引用計數(shù)加1
[dog retain];
NSLog(@"小狗的引用計數(shù)為 %ld",dog.retainCount);
//模擬:xiaoming不和小狗玩耍了,需要將其引用計數(shù)減1
[dog release];
NSLog(@"小狗的引用計數(shù)為 %ld",dog.retainCount);
//沒人需要和小狗玩耍了,將其引用計數(shù)減1
[dog release];
//將其指針置為nil,否則變成野指針
dog = nil;
}
return 0;
}
輸出結(jié)果為
2017-03-27 11:10:21.017977 TextARC[2645:84069] 小狗被派出去啦! 初始引用計數(shù)為 1
2017-03-27 11:10:21.018125 TextARC[2645:84069] 小狗的引用計數(shù)為 2
2017-03-27 11:10:21.018145 TextARC[2645:84069] 小狗的引用計數(shù)為 1
2017-03-27 11:10:21.018159 TextARC[2645:84069] 小狗回到寵物中心
可以看到,引用技術(shù)幫助寵物中心很好的標記了小狗的使用狀態(tài),在完成任務(wù)的時候及時收回到寵物中心。
思考幾個問題:
- NSString 引用技術(shù)問題
如果我們嘗試查看一個string的引用技術(shù)
NSString * str = @"hello guys";
NSLog(@"%ld",str.retainCount);
打印結(jié)果:
2017-03-27 11:14:33.191171 TextARC[2715:86088] str的引用計數(shù): -1
str的引用計數(shù)為-1,這可以理解為NSString實際上是一個字符串常量,是沒有引用計數(shù)的(或者它的引用計數(shù)是一個很大的值(使用%lu可以打印查看),對它做引用計數(shù)操作沒實質(zhì)上的影響)。
- 賦值不會擁有某個對象
NSString * name = dog.name;
這里僅僅是指針賦值操作,并不會增加name的引用計數(shù),需要持有對象必須要發(fā)送retain消息
dealloc
由于釋放對象是會調(diào)用dealloc方法,因此重寫dealloc方法來查看對象釋放的情況,如果沒有調(diào)用則會造成內(nèi)存泄露。在上面的例子中我們通過重寫dealloc讓小狗被釋放的時候打印日志來告訴我們已經(jīng)完成釋放。在上面的例子中,如果我們增加這樣一個操作
//沒人需要和小狗玩耍了,將其引用計數(shù)減1
[dog release];
NSLog(@"小狗的引用計數(shù)為·:%ld",dog.retainCount);
會發(fā)現(xiàn)獲取到的引用計數(shù)為1,為什么不是0呢?
這是因為對引用計數(shù)為1的對象release時,系統(tǒng)知道該對象將被回收,就不會再對該對象的引用計數(shù)進行減1操作,這樣可以增加對象回收的效率。
另外,對已釋放的對象發(fā)送消息是不可取的,因為對象的內(nèi)存已被回收,如果發(fā)送消息時,該內(nèi)存已經(jīng)被其他對象使用了,得到的結(jié)果是無法確定的,甚至會造成崩潰。
2. 自動釋放池
現(xiàn)在已經(jīng)明確了,當不再使用一個對象時應該將其釋放,但是在某些情況下,我們很難理清一個對象什么時候不再使用(比如xiaoming和小狗玩耍結(jié)束的時間不確定),這可怎么辦
ObjC提供autorelease方法來解決這個問題,當給一個autorelease消息時,方法會在未來某個時間給這個對象發(fā)送release消息將其釋放,在這個時間段內(nèi),對象還是可以使用的。
那autorelease的原理是什么呢
原理就是對象接收到autorelease消息時,它會被添加到了當前的自動釋放池中,當自動釋放池被銷毀時,會給池里所有對象發(fā)送release消息。
這里就引出了自動釋放池這個概念,什么是自動釋放池呢?顧名思義,就是一個池,這個池可以容納對象,而且可以自動釋放,這就大大增加了我們處理對象的靈活性。
自動釋放池怎么創(chuàng)建?
ObjC提供兩種方法創(chuàng)建自動釋放池:
- 方法一:使用
NSAutoreleasePool來創(chuàng)建
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc]init];
- 方法二:使用
@autoreleasepool創(chuàng)建
@autoreleasepool {
//這里寫代碼
}
自動釋放池創(chuàng)建后,就會成為活動的池子,釋放池子后,池子將釋放其所包含的所有對象。
以上兩種方法推薦第一種,因為將內(nèi)存交給ObjC管理更高效
自動釋放池什么時候創(chuàng)建?
app使用過程中,會定期自動生成和銷毀自動釋放池,一般是在程序事件處理之前創(chuàng)建,當然我們也可以自行創(chuàng)建自動釋放池,來達到我們一些特定的目的。
自動釋放池什么時候銷毀?
自動釋放池的銷毀時間是確定的,一般是在程序事件處理之后釋放,或者由我們自己手動釋放。
下面舉例說明自動釋放池的工作流程:
場景:現(xiàn)在xiaoming和xiaohong都想和小狗一起玩耍,但是他們的需求不一樣,他們的玩耍時間不一樣,流程如下:
- 方法一:
//
// main.m
// TextARC
//
// Created by taobaichi on 2017/3/27.
// Copyright ? 2017年 MaChao. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "Dog.h"
int main(int argc, const char * argv[]) {
//創(chuàng)建一個自動釋放池
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc]init];
//模擬:寵物中心派出小狗
Dog * dog = [[Dog alloc]init];
//模擬:xiaoming需要和小狗玩耍,需要將其引用計數(shù)加1
[dog retain];
NSLog(@"xiaoming和小狗玩耍,小狗的引用計數(shù)為 %ld",dog.retainCount);
//模擬:xiaohong需要和小狗玩耍,需要將其引用計數(shù)加1
[dog retain];
NSLog(@"xiaohong和小狗玩耍,小狗的引用技術(shù)為 %ld",dog.retainCount);
//模擬:xiaoming確定不想和小狗玩耍了,需要將其引用計數(shù)減1
[dog release];
NSLog(@"xiaoming確定不想和小狗玩了,小狗的引用計數(shù):%ld",dog.retainCount);
//模擬:xiaohong不確定何時不想和小狗玩耍了,將其設(shè)置為自動釋放
[dog autorelease];
NSLog(@"加入自動釋放池,小狗的引用計數(shù): %ld",dog.retainCount);
[dog release];
NSLog(@"釋放池子");
[pool release];
return 0;
}
- 方法二:
//
// main.m
// TextARC
//
// Created by taobaichi on 2017/3/27.
// Copyright ? 2017年 MaChao. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "Dog.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
//模擬:寵物中心派出小狗
Dog * dog = [[Dog alloc]init];
//模擬:xiaoming需要和小狗玩耍,需要將其引用計數(shù)加1
[dog retain];
NSLog(@"xiaoming需要和小狗玩耍,小狗的引用計數(shù)為 %ld",dog.retainCount);
//模擬:xiaohong需要和小狗玩耍,需要將其引用計數(shù)加1
[dog retain];
NSLog(@"xiaohong需要和小狗玩耍,小狗的引用計數(shù): %ld",dog.retainCount);
//模擬:xiaoming確定不想和小狗玩耍了,需要將其引用技術(shù)減1
[dog release];
NSLog(@"xiaoming確定不想和小狗玩耍了,小狗的引用計數(shù): %ld",dog.retainCount);
//模擬:xiaohong不確定何時不想和小狗玩耍了,將其設(shè)置為自動釋放
[dog autorelease];
NSLog(@"加入自動釋放池,小狗的引用計數(shù): %ld",dog.retainCount);
//沒人需要和小狗玩耍了,將其引用技術(shù)減1
[dog release];
NSLog(@"釋放池子");
}
return 0;
}
輸出結(jié)果如下(兩種方法輸出結(jié)果完全一致):
2017-03-27 14:10:40.808032 TextARC[3643:121392] 小狗被派出去啦! 初始引用計數(shù)為 1
2017-03-27 14:10:40.808204 TextARC[3643:121392] xiaoming和小狗玩耍,小狗的引用計數(shù)為 2
2017-03-27 14:10:40.808227 TextARC[3643:121392] xiaohong和小狗玩耍,小狗的引用技術(shù)為 3
2017-03-27 14:10:40.808246 TextARC[3643:121392] xiaoming不想和小狗玩了,小狗的引用計數(shù):2
2017-03-27 14:10:40.808269 TextARC[3643:121392] 加入自動釋放池,小狗的引用計數(shù): 2
2017-03-27 14:10:40.808291 TextARC[3643:121392] 釋放池子
2017-03-27 14:10:40.808339 TextARC[3643:121392] 小狗回到寵物中心
可以看到,當池子釋放后,dog對象才被釋放,因此在池子釋放之前,xiaohong都可以盡情地和小狗玩耍
使用自動釋放池需要注意:
自動釋放池實質(zhì)上只是在釋放的時候給池子中所有對象發(fā)送
release消息,不保證對象一定會銷毀,如果自動釋放池向?qū)ο蟀l(fā)送release消息后對象的引用計數(shù)仍大于1,對象就無法銷毀。自動釋放池中的對象會集中 在同一時間釋放,如果操作需要生成的對象較多占用內(nèi)存空間大,可以使用多個釋放池來進行優(yōu)化,比如在一個循環(huán)中需要創(chuàng)建大量的臨時變量,可以創(chuàng)建內(nèi)部的池子降低占用內(nèi)存峰值。
-autorelease不會改變對象的引用計數(shù)
自動釋放池的常見問題:
在管理對象釋放的問題上,自動釋放池幫助我們節(jié)省了大量的時間,但是有時候它卻未必會達到我們期望的效果,比如在一個循環(huán)事件中,如果循環(huán)次數(shù)較大或者事件處理占用內(nèi)存較大,就會導致內(nèi)存占用不斷增長,可能會導致不希望看到的后果。
示例代碼:
for (int i = 0; i < 100000; i++) {
NSString * log = [NSString stringWithFormat:@"%d",i];
NSLog(@"%@",log);
}
前面講過,自動釋放池的釋放時間是確定的,這個例子中自動釋放池會在循環(huán)事件結(jié)束時釋放,那么問題來了:在這個十萬次的循環(huán)中,每次都會生成一個字符串并打印,這些字符串對象都放在池子中直到循環(huán)結(jié)束才會釋放,因此在循環(huán)期間內(nèi)存不增長。
這類問題的解決方案是在循環(huán)中創(chuàng)建新的自動釋放池,多少個就你和釋放一次由我們自行決定。
for (int i = 0; i < 100000; i++) {
@autoreleasepool {
NSString * log = [NSString stringWithFormat:@"%d",i];
NSLog(@"%@",log);
}
}
3. iOS的內(nèi)存管理規(guī)則
3.1 基本原則
- 當你通過
new、alloc或copy方法創(chuàng)建一個對象時,它的引用計數(shù)為1,當不再使用該對象時應該向?qū)ο蟀l(fā)送release或者autorelease消息釋放對象。 - 當你通過其他方法獲得一個對象時,如果對象引用計數(shù)為1且被設(shè)置為
autorelease,則不需要執(zhí)行任何釋放對象的操作 - 如果你打算取得對象所有權(quán),就需要保留對象并在操作完成之后釋放,且必須保證
retain和release次數(shù)對等
應用到文章開頭的例子中,小朋友每申請一個小狗(生成對象),最后都要歸還到寵物中心(釋放對象),如果只申請而不歸還(對象創(chuàng)建了沒有釋放),那寵物中心的小狗就會越來越少(可用內(nèi)存越來越少),到最后一個小狗都沒有了(內(nèi)存被耗盡),其他小朋友就再也沒有小狗可申請了(無資源可使用),因此,必須要遵守規(guī)則:申請必須歸還(規(guī)則1),申請幾個必須歸還幾個(規(guī)則3),如果小狗被設(shè)定歸還時間則不用小朋友主動歸還(規(guī)則2)。
3.2 ARC
在MRC時代,必須嚴格遵守以上規(guī)則,否則內(nèi)存問題將成為惡魔一樣的存在,然而來到ARC時代,事情似乎變得輕松了,不用再寫無止盡的retain和release似乎讓開發(fā)變得輕松,對初學者變得更友好。
ObjC2.0引入了垃圾回收機制,然而由于垃圾回收機制會對移動設(shè)備產(chǎn)生某些不好的影響(例如由于垃圾清理造成的卡頓),iOS并不支持這個機制,蘋果的解決方案就是ARC(自動引用計數(shù))。
iOS5 以后,我們可以開啟ARC模式,ARC可以理解成一位管家,這個管家會幫我們向?qū)ο蟀l(fā)送retain和release語句,不再需要我們手動添加了,我們可以更舒心地創(chuàng)建或引用對象,簡化內(nèi)存管理步驟,節(jié)省大量的開發(fā)時間。
實際上,ARC并不是垃圾回收,也并不是不需要內(nèi)存管理了,它是隱式的內(nèi)存管理,編譯器在編譯的時候會在代碼插入合適的retain和release語句,相當于在背后幫我們完成了內(nèi)存管理的工作。
注意:
- 如果你的工程歷史比較悠久,可以將其從MRC轉(zhuǎn)換成ARC,跟上時代的步伐更好的維護
- 如果你的工程引用了某些不支持ARC的庫,可以在
Build Phases的Compile Sources將對應的.m文件的編譯器參數(shù)配置為-fno-objc-arc - ARC能幫我們簡化內(nèi)存管理,但不代表它是萬能的,還是有它不能處理的情況,這些需要我們手動處理,比如循環(huán)引用、非ObjC對象、
Core Foundation中的malloc()或者free()等等
思考:MRC有什么缺點,ARC有什么局限性?
3.3 ARC的修飾符
ARC提供四種修飾符,分別是strong、weak、autoreleasing、unsafe_unretained。
__strong:強引用,持有所指向?qū)ο蟮乃袡?quán),無修飾符情況下的默認值。如需強制釋放,可置nil。
比如我們常用的定時器
NSTimer * timer = [NSTimer timerWit...];
相當于
NSTimer * __strong timer = [NSTimer timerWith...];
當不需要使用的時候,強制銷毀定時器
[timer invalidate];
timer = nil;
__weak:弱引用,不持有所指向?qū)ο蟮乃袡?quán),引用指向的對象內(nèi)存被回收之后,引用本身會置nil,避免野指針.
比如避免循環(huán)引用的弱引用聲明:
__weak typeof(self)weakSelf = self;
__autoreleasing:自動釋放對象的引用,一般用于傳遞參數(shù)
比如一個讀取數(shù)據(jù)的方法
-(void)loadData:(NSError **)error
當你調(diào)用的時候會發(fā)現(xiàn)這樣的提示
NSError * error;
[self loadData:(NSError *__autoreleasing *)]
這是編譯器自動幫我們插入以下代碼
NSError * error;
NSError * __autoreleasing temErr = error;
[self loadData:&tmpErr];
__unsafe_unretained:為兼容iOS5以下版本的產(chǎn)物,可以理解成MRC下的weak,現(xiàn)在基本用不到,這里不做描述.
思考:
__strong NSTimer * timer和NSTimer * __strong timer哪個寫法是正確的,為什么編譯器不報錯?使用
__autoreleasing可能會遇到哪些問題?
3.4屬性的內(nèi)存管理
ObjC2.0引入了@property,提供成員變量訪問方法、權(quán)限、環(huán)境、內(nèi)存管理類型的聲明,下面主要說明ARC中屬性的內(nèi)存管理.
屬性的參數(shù)分為三類,基本數(shù)據(jù)類型默認為(atomic,readwrite,assign),對象類型默認為(atomic,readwrite,strong),其中第三個參數(shù)就是該屬性的內(nèi)存管理方式修飾,修飾詞可以是以下之一:
-
assign: 直接賦值
assign 一般用來修飾基本數(shù)據(jù)類型
@property(nonatomic,assign)NSInteger count;
當然也可以修飾ObjC對象,但是不推薦,因為assign被修飾的對象釋放后,指針還是釋放前的內(nèi)存,在后續(xù)操作中可能會導致內(nèi)存問題引發(fā)崩潰。
-
retain:
release舊值,再retain新值(引用計數(shù)+1)
retain和strong一樣,都用來修飾ObjC對象
使用set方法賦值時,實質(zhì)上是會先保留新值,再釋放舊值,再設(shè)置新值,避免新舊值一樣時導致對象被釋放的問題
MRC寫法:
-(void)setCount:(NSInteger)count
{
[count retain];
[_count release];
_count = count;
}
ARC對應寫法:
-(void)setCount:(NSInteger)count
{
_count = count;
}
-
copy:
release舊值,再copy新值(拷貝內(nèi)容)
一般用來修飾String、Dict、Array等需要保護其封裝性的對象,尤其是在其內(nèi)容可變的情況下,因此會拷貝(深拷貝)一份內(nèi)容給屬性使用,避免可能造成的對源內(nèi)容進行改動。
使用set方法賦值時,實質(zhì)上是會先拷貝新值,再釋放舊值,再設(shè)置新值
實際上,遵守NSCopying的對象都可以使用copy,當然,如果你確定是要共用同一份可變內(nèi)容,你也可以使用strong或retain
@property (nonatomic, copy) NSString * name;
-
weak:
ARC新引入修飾詞,可代替assign,比assign多增加一個特性:置nil。
weak和strong一樣用來修飾ObjC對象。
使用set方法賦值時實質(zhì)上不保留新值,也不釋放舊值,只設(shè)置新值
比如常用的代理的聲明
@property (weak)id<myDelegate>delegate;
XIb控件額引用
@property (weak, nonatomic) IBOutlet UILabel *nickNameLabel;
-
strong:ARC新引入修飾詞,可代替
retain
可參照retain,這里不再做描述
思考:
- 各個屬性修飾詞和3.3中的修飾詞的對應關(guān)系
- 屬性的本質(zhì)是什么
3.5 block的內(nèi)存管理
iOS中使用block必須自己管理內(nèi)存,錯誤的內(nèi)存管理將導致循環(huán)引用等內(nèi)存泄露問題,這里主要說明在ARC下block聲明和使用的時候需要注意的兩點:
- 如果你使用@property去聲明一個
block的時候,一般使用copy來進行修飾(當然也可以不寫,編譯器自動進行copy操作),盡量不要使用retain。
@property (nonatomic, copy) void(^block)(NSData * data);
block會對內(nèi)部使用的對象進行強引用,因此在使用的時候應該確定不會引起循環(huán)引用,當然保險的做法就是添加弱引用標記。
__weak typeof(self)weakSelf = self;
深入了解:
-
block的內(nèi)部實現(xiàn)原理是什么 - 從內(nèi)存位置來看
block有幾種類型?他們的內(nèi)存管理方式各是怎樣的? - 對于不同類型的外部變量,
block的內(nèi)存管理都是怎樣的?
4 經(jīng)典內(nèi)存泄露及其解決方案
雖然ARC好處多多,然而也無法避免內(nèi)存泄露問題,下面介紹在ARC中常見的內(nèi)存泄露。
4.1 僵尸對象和野指針
僵尸對象:內(nèi)存已經(jīng)被回收的對象
野指針 :指向僵尸對象的指針,向野指針發(fā)送消息會導致崩潰
野指針錯誤形式在Xcode中通常表現(xiàn)為:Thread 1:EXC_BAD_ACCESS,因為你訪問了一塊已經(jīng)不屬于你的內(nèi)存
例子代碼:(沒有出現(xiàn)錯誤的話多運行幾遍,,因為獲取野指針指向的結(jié)果是不確定的)
Dog * dog = [[Dog alloc]init];
NSLog(@"---before");
NSLog(@"---%s",object_getClassName(dog));
[dog release];
NSLog(@"---after");
NSLog(@"---%s",object_getClassName(dog));
運行結(jié)果:
2017-03-29 13:35:42.806557 TextARC[3235:116238] ---before
2017-03-29 13:35:42.806763 TextARC[3235:116238] ---Dog
2017-03-29 13:35:42.806827 TextARC[3235:116238] 小狗回到寵物中心
2017-03-29 13:35:42.806845 TextARC[3235:116238] ---after
(11db)
可以看到,當運行到弟六行的時候崩潰了,并給出了EXC_BAD_ACCESS的提示。
解決方案:
對象已經(jīng)被釋放后,應將其指針置為空指針(沒有指向任何對象的指針,給空指針發(fā)送消息不會報錯)。
然而在實際開發(fā)中實際遇到EXC_BAD_ACCESS錯誤時,往往很難定位到錯誤點,幸好Xcode提供方便的工具給我們來定位及分析錯誤。
1.在produce - scheme - edit scheme - diagnostics中將zombie objects勾選上,下次再出現(xiàn)這樣的錯誤就可以準確定位了。
運行結(jié)果:
2017-03-29 13:35:42.806557 TextARC[3235:116238] ---before
2017-03-29 13:35:42.806763 TextARC[3235:116238] ---Dog
2017-03-29 13:35:42.806827 TextARC[3235:116238] 小狗回到寵物中心
2017-03-29 13:35:42.806845 TextARC[3235:116238] ---after
2017-03-29 13:35:42.806845 TextARC[3235:116238]_NSZombie_Dog
可以看到,當運行到第六行時并沒有崩潰,并給出了NSZOmbie的提示
- 在
Xcode-Open Developer Tool-Instruments打開工具集,選擇Zombies工具可以對已安裝的應用進行僵尸檢測。
4.2 循環(huán)引用
循環(huán)引用是ARC中最常出現(xiàn)的問題
一般來講循環(huán)引用也是可以使用工具來檢測的,分為兩種:
在
peoduct-Analyze中使用靜態(tài)分析來檢測代碼中可能存在循環(huán)引用的問題。在
Xcode-Open Developer Tool-Instruments打開工具集,選擇Leaks工具可以對已安裝的應用進行內(nèi)存泄露檢測,此工具能檢測靜態(tài)分析不會提示,但是到運行時才會出現(xiàn)的內(nèi)存泄露問題。
Leaks工具雖然強大,但是它不能檢測到block循環(huán)引用導致的內(nèi)存泄露,這種情況一般需要自行排查問題,傻瓜式的方案當然是重寫對象的dealloc方法來監(jiān)測對象是否正常釋放,來確認沒有形成循環(huán)引用.
4.3 循環(huán)中對象占用內(nèi)存大
這個問題常見于循環(huán)次數(shù)較大,循環(huán)體生成的對象占用內(nèi)存較大的情景。
代碼示例:
for (int i = 0; i < 10000; i++) {
Dog * dog = [[Dog alloc]init];
[dog eat];
}
該循環(huán)內(nèi)產(chǎn)生大量的臨時對象,直至循環(huán)結(jié)束才釋放,可能導致內(nèi)存泄露,解決方法方法和自動釋放池常見問題類似,在循環(huán)中創(chuàng)建自己額autoreleasePool,及時釋放占用內(nèi)存大的臨時變量,減少內(nèi)存占用峰值。
for (int i = 0; i < 10000; i++) {
@autoreleasepool {
Dog * dog = [[Dog alloc]init];
[dog eat];
}
}
當然有時候autoreleasePool也不是萬能的
例子:假如有20000張圖片,每張1M左右,現(xiàn)在要獲取所有圖片的尺寸,你會怎么做?
如果這樣做
for (int i = 0; i < 2000; i++) {
CGSize size = [UIImage imageNamed:[NSString stringWithFormat:@"%d",i]].size;
}
用imageNamed方法加載圖片占用Cache的內(nèi)存,autoreleasePool也不能釋放,對此問題需要另外的解決辦法,最保險的當然是雙管齊下了
for (int i = 0; i < 2000; i++) {
@autoreleasepool {
CGSize size = [UIImage imageWithContentsOfFile:filePath].size;
}
}
4.4 無限循環(huán)
這個是比4.3更極端的情況,無論你出于什么原因,當你啟動了一個無限循環(huán)的時候,ARC會默認該方法不會執(zhí)行完畢,方法里面的對象就永不釋放,內(nèi)存無限上漲,導致內(nèi)存泄露
代碼示例:
NSLog(@"start!");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
BOOL isSucc = YES;
while (isSucc) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"create an obj");
}
});
輸出結(jié)果為
2017-03-29 15:00:41.371 duiduipeng[4311:156775] start!
2017-03-29 15:00:42.440 duiduipeng[4311:157083] create an obj
2017-03-29 15:00:43.514 duiduipeng[4311:157083] create an obj
2017-03-29 15:00:44.552 duiduipeng[4311:157083] create an obj
2017-03-29 15:00:45.625 duiduipeng[4311:157083] create an obj
2017-03-29 15:00:46.626 duiduipeng[4311:157083] create an obj
2017-03-29 15:00:47.696 duiduipeng[4311:157083] create an obj
2017-03-29 15:00:48.770 duiduipeng[4311:157083] create an obj
2017-03-29 15:00:49.836 duiduipeng[4311:157083] create an obj
可以看到,當控制器釋放后該循環(huán)還在繼續(xù)
對于這類問題解決方案是什么呢?
提示:解決方法有autoreleasePool、block、timer等