引用:http://www.reibang.com/p/556ba33fa498
之前發(fā)了一篇關(guān)于圖片加載優(yōu)化的文章蒸绩,還是引起很多人關(guān)注的肛捍,不過也有好多人反饋看不太懂雹锣,這次談?wù)刬OS中ARC的一些使用注意事項(xiàng)掖疮,相信做iOS開發(fā)的不會(huì)對ARC陌生啦耳高。
這里不是談ARC的使用扎瓶,只是介紹下ARC下仍然可能發(fā)生的內(nèi)存泄露問題,可能不全泌枪,歡迎大家補(bǔ)充概荷。
Ps:關(guān)于ARC的使用以及內(nèi)存管理問題,強(qiáng)烈建議看看官方文檔碌燕,里面對內(nèi)存管理的原理有很詳細(xì)的介紹误证,相信用過MRC的一定看過這個(gè)。
另也有簡單實(shí)用的ARC使用教程:ARC Best Practices
在2011年的WWDC中修壕,蘋果提到90%的crash是由于內(nèi)存管理引起的愈捅,ARC(Automatic Reference Counting)就是蘋果給出的解決方案。啟用ARC后慈鸠,開發(fā)者不需要擔(dān)心內(nèi)存管理蓝谨,編譯器會(huì)為你處理這一切(注意ARC是編譯器特性,而不是iOS運(yùn)行時(shí)特性,更不是其他語言中的垃圾收集器)譬巫。
簡單來說咖楣,編譯器在編譯代碼時(shí),會(huì)自動(dòng)生成實(shí)例的引用計(jì)數(shù)代碼芦昔,幫助我們完成之前MRC需要完成的工作诱贿,不過據(jù)說除此之外,編譯器也會(huì)執(zhí)行某些優(yōu)化咕缎。
ARC雖然能夠解決大部分的內(nèi)存泄露問題瘪松,但是仍然有些地方是我們需要注意的。
循環(huán)引用
When a block is copied, it creates strong references to object variables used within the block. If you use a block within the implementation of a method:
If you access an instance variable by reference, a strong reference is made to self;
If you access an instance variable by value, a strong reference is made to the variable.
主要有兩條規(guī)則:
第一條規(guī)則锨阿,如果在block中訪問了屬性宵睦,那么block就會(huì)retain住self。
第二條規(guī)則墅诡,如果在block中訪問了一個(gè)局部變量壳嚎,那么block就會(huì)對該變量有一個(gè)強(qiáng)引用,即retain該局部變量末早。
根據(jù)這兩條規(guī)則烟馅,我們可以知道發(fā)生循環(huán)引用的情況:
//規(guī)則1
self.myblock = ^{
[self doSomething]; // 訪問成員方法
NSLog(@"%@", weakSelf.str); // 訪問屬性
};
//規(guī)則2
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setCompletionBlock:^{
NSString* string = [request responseString];
}];
對象對block擁有一個(gè)強(qiáng)引用,而block內(nèi)部又對外部對象有一個(gè)強(qiáng)引用然磷,形成了閉環(huán)郑趁,發(fā)生內(nèi)存泄露。
怎么解決這種內(nèi)存泄露呢姿搜?
可以用block變量來解決寡润,首先還是看看官方文檔怎么說的:
Use Lifetime Qualifiers to Avoid Strong Reference Cycles: https://developer.apple.com/library/ios/releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html
In manual reference counting mode, __block id x; has the effect of not retaining x. In ARC mode, __block id x; defaults to retaining x (just like all other values). To get the manual reference counting mode behavior under ARC, you could use __unsafe_unretained __block id x;. As the name __unsafe_unretained implies, however, having a non-retained variable is dangerous (because it can dangle) and is therefore discouraged. Two better options are to either use __weak (if you don’t need to support iOS 4 or OS X v10.6), or set the __block value to nil to break the retain cycle.
官網(wǎng)提供了幾種方案,我們看看第一種舅柜,用__block變量:
在MRC中梭纹,block id x不會(huì)retain住x;但是在ARC中致份,默認(rèn)是retain住x的变抽,我們需要使用unsafe_unretained __block id x來達(dá)到弱引用的效果。
那么解決方案就如下所示:
__block id weakSelf = self; //MRC
//__unsafe_unretained __block id weakSelf = self; ARC下面用這個(gè)
self.myblock = ^{
[weakSelf doSomething];
NSLog(@"%@", weakSelf.str);
};
performSelector的問題
[self performSelector:@selector(foo:) withObject:self.property afterDelay:3];
performSelector延時(shí)調(diào)用的原理是這樣的,執(zhí)行上面這段函數(shù)的時(shí)候系統(tǒng)會(huì)自動(dòng)將self.property的retainCount加1氮块,直到selector執(zhí)行完畢之后才會(huì)將self.property的retainCount減1绍载。這樣子如果selector一直未執(zhí)行的話,self就一直不能夠被釋放掉滔蝉,就有可能照成內(nèi)存泄露击儡。比較好的解決方案是將未執(zhí)行的perform給取消掉:
[NSObject cancelPreviousPerformRequestsWithTarget:self];
因這種原因產(chǎn)生的泄露因?yàn)椴⒉贿`反任何規(guī)則,是Intrument所無法發(fā)現(xiàn)的锰提。
NSTimer的問題
我們都知道timer用來在未來的某個(gè)時(shí)刻執(zhí)行一次或者多次我們指定的方法曙痘,那么問題來了(當(dāng)然不是挖掘機(jī))芳悲。究竟系統(tǒng)是怎么保證timer觸發(fā)action的時(shí)候,我們指定的方法是有效的呢边坤?萬一receiver無效了呢名扛?
答案很簡單,系統(tǒng)會(huì)自動(dòng)retain住其接收者茧痒,直到其執(zhí)行我們指定的方法肮韧。
看看官方的文檔吧,也建議你自己寫個(gè)demo測試一下旺订。
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats
target
The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to target until it (the timer) is invalidated. (系統(tǒng)會(huì)維護(hù)一個(gè)強(qiáng)引用直到timer調(diào)用invalidated)
userInfo
The user info for the timer. The timer maintains a strong reference to this object until it (the timer) is invalidated. This parameter may be nil.
repeats
If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires.
可以注意到repeats參數(shù)弄企,一次性(repeats為NO)的timer會(huì)再觸發(fā)后自動(dòng)調(diào)用invalidated,而重復(fù)性的timer則不會(huì)区拳。
現(xiàn)在問題又來了拘领,看看下面這段代碼:
- (void)dealloc
{
[timer invalidate];
[super dealloc];
}
這個(gè)是很容易犯的錯(cuò)誤,如果這個(gè)timer是個(gè)重復(fù)性的timer樱调,那么self對象就會(huì)被timerretain住约素,這個(gè)時(shí)候不調(diào)用invalidate的話,self對象的引用計(jì)數(shù)會(huì)大于1笆凌,dealloc永遠(yuǎn)不會(huì)調(diào)用到圣猎,這樣內(nèi)存泄露就會(huì)發(fā)生。
timer都會(huì)對它的target進(jìn)行retain乞而,我們需要小心對待這個(gè)target的生命周期問題送悔,尤其是重復(fù)性的timer,同時(shí)需要注意在dealloc之前調(diào)用invalidate爪模。
關(guān)于timer其實(shí)有挺多可以研究的欠啤,比如其必須在runloop中才有效,比如其時(shí)間一定是準(zhǔn)的嗎呻右?這些由于和本章主題不相關(guān)跪妥,暫時(shí)就不說了。
關(guān)于performSelector:afterDelay的問題
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay
我們還是看看官方文檔怎么說的声滥,同樣也希望大家能寫個(gè)demo驗(yàn)證下。
This method sets up a timer to perform the aSelector message on the current thread’s run loop. The timer is configured to run in the default mode (NSDefaultRunLoopMode). When the timer fires, the thread attempts to dequeue the message from the run loop and perform the selector. It succeeds if the run loop is running and in the default mode; otherwise, the timer waits until the run loop is in the default mode.
大概意思是系統(tǒng)依靠一個(gè)timer來保證延時(shí)觸發(fā)侦香,但是只有在runloop在default mode的時(shí)候才會(huì)執(zhí)行成功落塑,否則selector會(huì)一直等待run loop切換到default mode。
根據(jù)我們之前關(guān)于timer的說法罐韩,在這里其實(shí)調(diào)用performSelector:afterDelay:同樣會(huì)造成系統(tǒng)對target強(qiáng)引用憾赁,也即
retain住。這樣子散吵,如果selector一直無法執(zhí)行的話(比如runloop不是運(yùn)行在default model下),這樣子同樣會(huì)造成target一直無法被釋放掉龙考,發(fā)生內(nèi)存泄露蟆肆。
怎么解決這個(gè)問題呢?
其實(shí)很簡單晦款,我們在適當(dāng)?shù)臅r(shí)候取消掉該調(diào)用就行了炎功,系統(tǒng)提供了接口:
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget
這個(gè)函數(shù)可以在dealloc中調(diào)用嗎,大家可以自己思考下缓溅?
關(guān)于NSNotification的addObserver與removeObserver問題
我們應(yīng)該會(huì)注意到我們常常會(huì)再dealloc里面調(diào)用removeObserver,會(huì)不會(huì)上面的問題呢蛇损?
答案是否定的,這是因?yàn)閍ddObserver只會(huì)建立一個(gè)弱引用到接收者坛怪,所以不會(huì)發(fā)生內(nèi)存泄露的問題淤齐。但是我們需要在dealloc里面調(diào)用removeObserver,避免通知的時(shí)候袜匿,對象已經(jīng)被銷毀更啄,這時(shí)候會(huì)發(fā)生crash.
C 語言的接口
C 語言不能夠調(diào)用OC中的retain與release,一般的C 語言接口都提供了release函數(shù)(比如CGContextRelease(context c))來管理內(nèi)存居灯。ARC不會(huì)自動(dòng)調(diào)用這些C接口的函數(shù)祭务,所以這還是需要我們自己來進(jìn)行管理的.
下面是一段常見的繪制代碼,其中就需要自己調(diào)用release接口穆壕。
CGContextRef context = CGBitmapContextCreate(NULL, target_w, target_h, 8, 0, rgb, bmi);
CGColorSpaceRelease(rgb);
UIImage *pdfImage = nil;
if (context != NULL) {
CGContextDrawPDFPage(context, page);
CGImageRef imageRef = CGBitmapContextCreateImage(context);
CGContextRelease(context);
pdfImage = [UIImage imageWithCGImage:imageRef scale:screenScale orientation:UIImageOrientationUp];
CGImageRelease(imageRef);
} else {
CGContextRelease(context);
}
總的來說待牵,ARC還是很好用的,能夠幫助你解決大部分的內(nèi)存泄露問題喇勋。所以還是推薦大家直接使用ARC缨该,盡量不要使用mrc。