ARC下需要注意的內(nèi)存管理

之前發(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:

  1. If you access an instance variable by reference, a strong reference is made to self;
  2. 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ā),但是只有在runloopdefault 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)

  1. Transitioning to ARC Release Notes
  2. iOS應(yīng)用開(kāi)發(fā):什么是ARC?
  3. Blocks, Operations, and Retain Cycles
  4. iOS7.0 使用ARC
  5. block使用小結(jié)疆股、在arc中使用block费坊、如何防止循環(huán)引用
  6. IOS中關(guān)于NSTimer使用知多少
  7. 正確使用Block避免Cycle Retain和Crash

轉(zhuǎn)載請(qǐng)注明出處哦,我的博客:luoyibu

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末旬痹,一起剝皮案震驚了整個(gè)濱河市附井,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌两残,老刑警劉巖永毅,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異人弓,居然都是意外死亡沼死,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)崔赌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)意蛀,“玉大人,你說(shuō)我怎么就攤上這事健芭∠卦浚” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵慈迈,是天一觀的道長(zhǎng)魁蒜。 經(jīng)常有香客問(wèn)我,道長(zhǎng)吩翻,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任锥咸,我火速辦了婚禮狭瞎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘搏予。我一直安慰自己熊锭,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著碗殷,像睡著了一般精绎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上锌妻,一...
    開(kāi)封第一講書(shū)人閱讀 51,155評(píng)論 1 299
  • 那天代乃,我揣著相機(jī)與錄音,去河邊找鬼仿粹。 笑死搁吓,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的吭历。 我是一名探鬼主播堕仔,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼晌区!你這毒婦竟也來(lái)了摩骨?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤朗若,失蹤者是張志新(化名)和其女友劉穎恼五,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體捡偏,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡唤冈,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了银伟。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片你虹。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖彤避,靈堂內(nèi)的尸體忽然破棺而出傅物,到底是詐尸還是另有隱情,我是刑警寧澤琉预,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布董饰,位于F島的核電站,受9級(jí)特大地震影響圆米,放射性物質(zhì)發(fā)生泄漏卒暂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一娄帖、第九天 我趴在偏房一處隱蔽的房頂上張望也祠。 院中可真熱鬧,春花似錦近速、人聲如沸诈嘿。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)奖亚。三九已至淳梦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間昔字,已是汗流浹背爆袍。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留李滴,地道東北人螃宙。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像所坯,于是被迫代替她去往敵國(guó)和親谆扎。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 引用:http://www.reibang.com/p/556ba33fa498 之前發(fā)了一篇關(guān)于圖片加載優(yōu)化的文...
    飛天豬Pony閱讀 243評(píng)論 0 1
  • ARC的本質(zhì) ARC是編譯器(時(shí))特性芹助,而不是運(yùn)行時(shí)特性堂湖,更不是垃圾回收器(GC)。 Automatic Refe...
    成熱了閱讀 616評(píng)論 0 1
  • Cocoa內(nèi)存管理機(jī)制 (1)當(dāng)你使用new状土、alloc无蜂、copy方法創(chuàng)建一個(gè)對(duì)象時(shí),該對(duì)象的保留計(jì)數(shù)器值為1.當(dāng)...
    John_LS閱讀 2,773評(píng)論 0 6
  • 內(nèi)存管理 簡(jiǎn)述OC中內(nèi)存管理機(jī)制蒙谓。與retain配對(duì)使用的方法是dealloc還是release斥季,為什么?需要與a...
    丶逐漸閱讀 1,963評(píng)論 1 16
  • 姓名:巴桂成 公司:寧波大發(fā)化纖有限公司 寧波盛和塾《六項(xiàng)精進(jìn)》235期學(xué)員 【日精進(jìn)打卡第92天】 【知~學(xué)習(xí)】...
    巴桂成_c6dd閱讀 158評(píng)論 0 0