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

引用: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。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末川背,一起剝皮案震驚了整個(gè)濱河市贰拿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌熄云,老刑警劉巖膨更,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異缴允,居然都是意外死亡荚守,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進(jìn)店門练般,熙熙樓的掌柜王于貴愁眉苦臉地迎上來矗漾,“玉大人,你說我怎么就攤上這事薄料〕ü保” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵摄职,是天一觀的道長誊役。 經(jīng)常有香客問我获列,道長,這世上最難降的妖魔是什么蛔垢? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任击孩,我火速辦了婚禮,結(jié)果婚禮上啦桌,老公的妹妹穿的比我還像新娘溯壶。我一直安慰自己,他們只是感情好甫男,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布且改。 她就那樣靜靜地躺著,像睡著了一般板驳。 火紅的嫁衣襯著肌膚如雪又跛。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天若治,我揣著相機(jī)與錄音慨蓝,去河邊找鬼。 笑死端幼,一個(gè)胖子當(dāng)著我的面吹牛礼烈,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播婆跑,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼此熬,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了滑进?” 一聲冷哼從身側(cè)響起犀忱,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎扶关,沒想到半個(gè)月后阴汇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡节槐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年搀庶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片铜异。...
    茶點(diǎn)故事閱讀 39,703評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡地来,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出熙掺,到底是詐尸還是另有隱情,我是刑警寧澤咕宿,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布币绩,位于F島的核電站蜡秽,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏缆镣。R本人自食惡果不足惜芽突,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望董瞻。 院中可真熱鬧寞蚌,春花似錦、人聲如沸钠糊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽抄伍。三九已至艘刚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間截珍,已是汗流浹背攀甚。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留岗喉,地道東北人秋度。 一個(gè)月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像钱床,于是被迫代替她去往敵國和親荚斯。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評論 2 353

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

  • 之前發(fā)了一篇關(guān)于圖片加載優(yōu)化的文章诞丽,還是引起很多人關(guān)注的鲸拥,不過也有好多人反饋看不太懂,這次談?wù)刬OS中ARC的一些...
    一不閱讀 10,894評論 6 50
  • ARC的本質(zhì) ARC是編譯器(時(shí))特性僧免,而不是運(yùn)行時(shí)特性刑赶,更不是垃圾回收器(GC)。 Automatic Refe...
    成熱了閱讀 616評論 0 1
  • 內(nèi)存管理 簡述OC中內(nèi)存管理機(jī)制懂衩。與retain配對使用的方法是dealloc還是release撞叨,為什么?需要與a...
    丶逐漸閱讀 1,963評論 1 16
  • Cocoa內(nèi)存管理機(jī)制 (1)當(dāng)你使用new浊洞、alloc牵敷、copy方法創(chuàng)建一個(gè)對象時(shí),該對象的保留計(jì)數(shù)器值為1.當(dāng)...
    John_LS閱讀 2,773評論 0 6
  • Day79(2.20):62.6法希; Day88(3.1):63.1; Day89(3.6):63.7枷餐; 早餐:1杯...
    好西好閱讀 147評論 0 0