之前發(fā)了一篇關(guān)于圖片加載優(yōu)化的文章刑峡,還是引起很多人關(guān)注的纵菌,不過(guò)也有好多人反饋看不太懂斥赋,這次談?wù)刬OS中ARC的一些使用注意事項(xiàng),相信做iOS開(kāi)發(fā)的不會(huì)對(duì)ARC陌生啦产艾。
這里不是談ARC的使用疤剑,只是介紹下ARC下仍然可能發(fā)生的內(nèi)存泄露問(wèn)題,可能不全闷堡,歡迎大家補(bǔ)充隘膘。
Ps:關(guān)于ARC的使用以及內(nèi)存管理問(wèn)題,強(qiáng)烈建議看看官方文檔杠览,里面對(duì)內(nèi)存管理的原理有很詳細(xì)的介紹弯菊,相信用過(guò)MRC的一定看過(guò)這個(gè)。
另也有簡(jiǎn)單實(shí)用的ARC使用教程:ARC Best Practices
在2011年的WWDC中踱阿,蘋(píng)果提到90%的crash是由于內(nèi)存管理引起的管钳,ARC(
Automatic Reference Counting
)就是蘋(píng)果給出的解決方案。啟用ARC后软舌,開(kāi)發(fā)者不需要擔(dān)心內(nèi)存管理才漆,編譯器會(huì)為你處理這一切(注意ARC是編譯器特性,而不是iOS運(yùn)行時(shí)特性佛点,更不是其他語(yǔ)言中的垃圾收集器)醇滥。
簡(jiǎn)單來(lái)說(shuō),編譯器在編譯代碼時(shí)超营,會(huì)自動(dòng)生成實(shí)例的引用計(jì)數(shù)代碼鸳玩,幫助我們完成之前MRC需要完成的工作,不過(guò)據(jù)說(shuō)除此之外演闭,編譯器也會(huì)執(zhí)行某些優(yōu)化不跟。
ARC雖然能夠解決大部分的內(nèi)存泄露問(wèn)題,但是仍然有些地方是我們需要注意的米碰。
循環(huán)引用
循環(huán)引用簡(jiǎn)單來(lái)說(shuō)就是兩個(gè)對(duì)象相互強(qiáng)引用了對(duì)方
窝革,即retain了對(duì)方,從而導(dǎo)致誰(shuí)也釋放不了誰(shuí)的內(nèi)存泄露問(wèn)題见间。比如聲明一個(gè)delegate時(shí)一般用weak而不能用retain或strong聊闯,因?yàn)槟阋坏┠敲醋隽耍艽罂赡芤鹧h(huán)引用米诉。
這種簡(jiǎn)單的循環(huán)引用只要在coding的過(guò)程中多加注意菱蔬,一般都可以發(fā)現(xiàn)。
解決的辦法也很簡(jiǎn)單,一般是將循環(huán)鏈中的一個(gè)強(qiáng)引用改為弱引用就可解決拴泌。
另外一種block引起的循環(huán)引用問(wèn)題魏身,通常是一些對(duì)block原理不太熟悉的開(kāi)發(fā)者不太容易發(fā)現(xiàn)的問(wèn)題。
block引起的循環(huán)引用
我們先看看官方文檔關(guān)于block調(diào)用時(shí)的解釋:Object and Block Variables
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中訪問(wèn)了屬性箭昵,那么block就會(huì)retain住self。
第二條規(guī)則回季,如果在block中訪問(wèn)了一個(gè)局部變量家制,那么block就會(huì)對(duì)該變量有一個(gè)強(qiáng)引用,即retain該局部變量泡一。
根據(jù)這兩條規(guī)則颤殴,我們可以知道發(fā)生循環(huán)引用的情況:
//規(guī)則1
self.myblock = ^{
[self doSomething]; // 訪問(wèn)成員方法
NSLog(@"%@", weakSelf.str); // 訪問(wèn)屬性
};
//規(guī)則2
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setCompletionBlock:^{
NSString* string = [request responseString];
}];
對(duì)象對(duì)block擁有一個(gè)強(qiáng)引用,而block內(nèi)部又對(duì)外部對(duì)象有一個(gè)強(qiáng)引用鼻忠,形成了閉環(huán)涵但,發(fā)生內(nèi)存泄露。
怎么解決這種內(nèi)存泄露呢帖蔓?
可以用block變量來(lái)解決矮瘟,首先還是看看官方文檔怎么說(shuō)的:
Use Lifetime Qualifiers to Avoid Strong Reference Cycles
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來(lái)達(dá)到弱引用的效果。
那么解決方案就如下所示:
__block id weakSelf = self; //MRC
//__unsafe_unretained __block id weakSelf = self; ARC下面用這個(gè)
self.myblock = ^{
[weakSelf doSomething];
NSLog(@"%@", weakSelf.str);
};
NSTimer的問(wèn)題
我們都知道timer用來(lái)在未來(lái)的某個(gè)時(shí)刻執(zhí)行一次或者多次我們指定的方法奇瘦,那么問(wèn)題來(lái)了(當(dāng)然不是挖掘機(jī))。究竟系統(tǒng)是怎么保證timer觸發(fā)action的時(shí)候劲弦,我們指定的方法是有效的呢耳标?萬(wàn)一receiver無(wú)效了呢?
答案很簡(jiǎn)單邑跪,系統(tǒng)會(huì)自動(dòng)retain住其接收者次坡,直到其執(zhí)行我們指定的方法。
看看官方的文檔吧画畅,也建議你自己寫(xiě)個(gè)demo測(cè)試一下砸琅。
+ (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)在問(wèn)題又來(lái)了,看看下面這段代碼:
- (void)dealloc
{
[timer invalidate];
[super dealloc];
}
這個(gè)是很容易犯的錯(cuò)誤,如果這個(gè)timer是個(gè)重復(fù)性的timer诱篷,那么self
對(duì)象就會(huì)被timerretain
住壶唤,這個(gè)時(shí)候不調(diào)用invalidate
的話,self
對(duì)象的引用計(jì)數(shù)會(huì)大于1棕所,dealloc
永遠(yuǎn)不會(huì)調(diào)用到闸盔,這樣內(nèi)存泄露就會(huì)發(fā)生。
timer都會(huì)對(duì)它的target進(jìn)行retain琳省,我們需要小心對(duì)待這個(gè)target的生命周期問(wèn)題迎吵,尤其是重復(fù)性的timer,同時(shí)需要注意在dealloc之前調(diào)用invalidate针贬。
關(guān)于timer其實(shí)有挺多可以研究的击费,比如其必須在runloop中才有效,比如其時(shí)間一定是準(zhǔn)的嗎坚踩?這些由于和本章主題不相關(guān)荡灾,暫時(shí)就不說(shuō)了。
關(guān)于performSelector:afterDelay的問(wèn)題
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay
我們還是看看官方文檔怎么說(shuō)的瞬铸,同樣也希望大家能寫(xiě)個(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來(lái)保證延時(shí)觸發(fā),但是只有在runloop
在default mode
的時(shí)候才會(huì)執(zhí)行成功嗓节,否則selector
會(huì)一直等待run loop
切換到default mode
荧缘。
根據(jù)我們之前關(guān)于timer
的說(shuō)法,在這里其實(shí)調(diào)用performSelector:afterDelay:同樣會(huì)造成系統(tǒng)對(duì)target
強(qiáng)引用拦宣,也即retain
住截粗。這樣子,如果selector
一直無(wú)法執(zhí)行的話(比如runloop
不是運(yùn)行在default model
下),這樣子同樣會(huì)造成target
一直無(wú)法被釋放掉鸵隧,發(fā)生內(nèi)存泄露绸罗。
怎么解決這個(gè)問(wèn)題呢?
其實(shí)很簡(jiǎn)單豆瘫,我們?cè)谶m當(dāng)?shù)臅r(shí)候取消掉該調(diào)用就行了珊蟀,系統(tǒng)提供了接口:
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget
這個(gè)函數(shù)可以在dealloc
中調(diào)用嗎,大家可以自己思考下外驱?
關(guān)于NSNotification的addObserver與removeObserver問(wèn)題
我們應(yīng)該會(huì)注意到我們常常會(huì)再dealloc
里面調(diào)用removeObserver
,會(huì)不會(huì)上面的問(wèn)題呢育灸?
答案是否定的,這是因?yàn)?code>addObserver只會(huì)建立一個(gè)弱引用到接收者昵宇,所以不會(huì)發(fā)生內(nèi)存泄露的問(wèn)題磅崭。但是我們需要在dealloc
里面調(diào)用removeObserver
,避免通知的時(shí)候瓦哎,對(duì)象已經(jīng)被銷毀砸喻,這時(shí)候會(huì)發(fā)生crash
.
C 語(yǔ)言的接口
C 語(yǔ)言不能夠調(diào)用OC中的retain與release柔逼,一般的C 語(yǔ)言接口都提供了release函數(shù)(比如CGContextRelease(context c))來(lái)管理內(nèi)存。ARC不會(huì)自動(dòng)調(diào)用這些C接口的函數(shù)恩够,所以這還是需要我們自己來(lái)進(jìn)行管理的.
下面是一段常見(jià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);
}
總的來(lái)說(shuō)蜂桶,ARC還是很好用的儡毕,能夠幫助你解決大部分的內(nèi)存泄露問(wèn)題。所以還是推薦大家直接使用ARC扑媚,盡量不要使用mrc腰湾。
參考文獻(xiàn)
- Transitioning to ARC Release Notes
- iOS應(yīng)用開(kāi)發(fā):什么是ARC?
- Blocks, Operations, and Retain Cycles
- iOS7.0 使用ARC
- block使用小結(jié)疆股、在arc中使用block费坊、如何防止循環(huán)引用
- IOS中關(guān)于NSTimer使用知多少
- 正確使用Block避免Cycle Retain和Crash
轉(zhuǎn)載請(qǐng)注明出處哦,我的博客:luoyibu