iOS開發ARC內存管理技術要點

1、ARC的本質

ARC是編譯器(時)特性,而不是運行時特性,更不是垃圾回收器(GC)。

標簽:內存暴漲、內存泄漏、autorelease、內存報警

2、ARC的開啟與關閉

如果需要對特定文件開啟或關閉ARC,可以在工程選項中選擇Targets -> Compile Phases -> Compile Sources,在里面找到對應文件,添加flag:

打開ARC: -fobjc-arc

關閉ARC :-fno-objc-arc

3、ARC的修飾符

ARC主要提供了4種修飾符,他們分別是:__strong,__weak,__autoreleasing,__unsafe_unretained

3.1、__strong

表示引用為強引用。對應在定義property時的"strong"。所有對象只有當沒有任何一個強引用指向時,才會被釋放。

注意:如果在聲明引用時不加修飾符,那么引用將默認是強引用。當需要釋放強引用指向的對象時,需要將強引用置nil。

3.2、__weak

表示引用為弱引用。對應在定義property時用的"weak"。弱引用不會影響對象的釋放,即只要對象沒有任何強引用指向,即使有100個弱引用對象指向也沒用,該對象依然會被釋放。不過好在,對象在被釋放的同時,指向它的弱引用會自動被置nil,這個技術叫zeroing weak pointer。這樣有效得防止無效指針、野指針的產生。__weak一般用在delegate關系中防止循環引用或者用來修飾指向由Interface Builder編輯與生成的UI控件。

3.3、__autoreleasing

表示在autorelease pool中自動釋放對象的引用,和MRC時代autorelease的用法相同。定義property時不能使用這個修飾符,任何一個對象的property都不應該是autorelease型的。

一個常見的誤解是,在ARC中沒有autorelease,因為這樣一個“自動釋放”看起來好像有點多余。這個誤解可能源自于將ARC的“自動”和autorelease“自動”的混淆。其實你只要看一下每個iOS App的main.m文件就能知道,autorelease不僅好好的存在著,并且變得更fashion了:不需要再手工被創建,也不需要再顯式得調用[drain]方法釋放內存池

以下兩行代碼的意義是相同的。

NSString?*str?=?[[[NSString?alloc]?initWithFormat:@"hehe"]?autorelease];?//?MRC

NSString?*__autoreleasing?str?=?[[NSString?alloc]?initWithFormat:@"hehe"];?//?ARC

__autoreleasing在ARC中主要用在參數傳遞返回值(out-parameters)和引用傳遞參數(pass-by-reference)的情況下。

按照這個說明,要定義一個weak型的NSString引用,它的寫法應該是:

NSString?*?__weak?str?=?@"hehe";?//?正確!

而不應該是:

__weak?NSString?*str?=?@"hehe";??//?錯誤!

4、ARC與Block

在MRC時代,Block會隱式地對進入其作用域內的對象(或者說被Block捕獲的指針指向的對象)加retain,來確保Block使用到該對象時,能夠正確的訪問。

MAR下的

在這段代碼中,myController的completionHandler調用了myController的方法[dismissViewController...],這時completionHandler會對myController做retain操作。而我們知道,myController對completionHandler也至少有一個retain(一般準確講是copy),這時就出現了在內存管理中最糟糕的情況:循環引用!簡單點說就是:myController retain了completionHandler,而completionHandler也retain了myController。循環引用導致了myController和completionHandler最終都不能被釋放。我們在delegate關系中,對delegate指針用weak就是為了避免這種問題。

對這種情況,我們一般用如下方法解決:給要進入Block的指針加一個__block修飾符

這個__block在MRC時代有兩個作用:

說明變量可改

說明指針指向的對象不做這個隱式的retain操作

一個變量如果不加__block,是不能在Block里面修改的,不過這里有一個例外:static的變量和全局變量不需要加__block就可以在Block中修改

使用這種方法,我們對代碼做出修改,解決了循環引用的問題:


MRC下的解決循環引用


在ARC引入后,沒有了retain和release等操作,情況也發生了改變:在任何情況下,__block修飾符的作用只有上面的第一條:說明變量可改。即使加上了__block修飾符,一個被block捕獲的強引用也依然是一個強引用。這樣在ARC下,如果我們還按照MRC下的寫法,completionHandler對myController有一個強引用,而myController對completionHandler有一個強引用,這依然是循環引用,沒有解決問題:

于是我們還需要對原代碼做修改。簡單的情況我們可以這樣寫:

ARC下解決循環引用粗暴方案

在completionHandler之后將myController指針置nil,保證了completionHandler對myController強引用的解除,不過也同時解除了myController對myController對象的強引用。這種方法過于簡單粗暴了,在大多數情況下,我們有更好的方法。

這個更好的方法就是使用weak。(或者為了考慮iOS4的兼容性用unsafe_unretained,具體用法和weak相同,考慮到現在iOS4設備可能已經絕跡了,這里就不講這個方法了)(關于這個方法的本質我們后面會談到)

為了保證completionHandler這個Block對myController沒有強引用,我們可以定義一個臨時的弱引用weakMyViewController來指向原myController的對象,并把這個弱引用傳入到Block內,這樣就保證了Block對myController持有的是一個弱引用,而不是一個強引用。如此,我們繼續修改代碼:


ARC下簡單解決循環引用

這樣循環引用的問題就解決了,但是卻不幸地引入了一個新的問題:由于傳入completionHandler的是一個弱引用,那么當myController指向的對象在completionHandler被調用前釋放,那么completionHandler就不能正常的運作了。在一般的單線程環境中,這種問題出現的可能性不大,但是到了多線程環境,就很不好說了,所以我們需要繼續完善這個方法。

為了保證在Block內能夠訪問到正確的myController,我們在block內新定義一個強引用strongMyController來指向weakMyController指向的對象,這樣多了一個強引用,就能保證這個myController對象不會在completionHandler被調用前釋放掉了。于是,我們對代碼再次做出修改:


ARC下周全解決循環引用方案

官方文檔對這個問題的說明到這里就結束了,但是可能很多朋友會有疑問,不是說不希望Block對原myController對象增加強引用么,這里為啥堂而皇之地在Block內新定義了一個強引用,這個強引用不會造成循環引用么?理解這個問題的關鍵在于理解被Block捕獲的引用和在Block內定義的引用的區別。為了搞得明白這個問題,這里需要了解一些Block的實現原理,但由于篇幅的緣故,本文在這里就不展開了,詳細的內容可以參考其他的文章,這里特別推薦唐巧的文章和另外2位作者的博文:這個這個,講的都比較清楚。

5、ARC與Toll-Free Bridging

Toll-Free Briding保證了在程序中,可以方便和諧的使用Core Foundation類型的對象和Objective-C類型的對象。詳細的內容可參考官方文檔。以下是官方文檔中給出的一些例子:


Apple官網demo

在MRC時代,由于Objective-C類型的對象和Core Foundation類型的對象都是相同的release和retain操作規則,所以Toll-Free Bridging的使用比較簡單,但是自從ARC加入后,Objective-C類型的對象內存管理規則改變了,而Core Foundation依然是之前的機制,換句話說,Core Foundation不支持ARC。

這個時候就必須要要考慮一個問題了,在做Core Foundation與Objective-C類型轉換的時候,用哪一種規則來管理對象的內存。顯然,對于同一個對象,我們不能夠同時用兩種規則來管理,所以這里就必須要確定一件事情:哪些對象用Objective-C(也就是ARC)的規則,哪些對象用Core Foundation的規則(也就是MRC)的規則。或者說要確定對象類型轉換了之后,內存管理的ownership的改變。

5.1、__bridge(修飾符)

只是聲明類型轉變,但是不做內存管理規則的轉變。

CFStringRef?s1?=?(__bridge?CFStringRef)?[[NSString?alloc]?initWithFormat:@"Hello,?%@!",?name];

只是做了NSString到CFStringRef的轉化,但管理規則未變,依然要用Objective-C類型的ARC來管理s1,你不能用CFRelease()去釋放s1。

5.2、__bridge_retained(修飾符)?or?CFBridgingRetain(函數)

表示將指針類型轉變的同時,將內存管理的責任由原來的Objective-C交給Core Foundation來處理,也就是,將ARC轉變為MRC。

NSString?*s1?=?[[NSString?alloc]?initWithFormat:@"Hello,?%@!",?name];

?CFStringRef?s2?=?(__bridge_retained?CFStringRef)s1;

?//?do?something?with?s2

//...

?CFRelease(s2);?//?注意要在使用結束后加這個

我們在第二行做了轉化,這時內存管理規則由ARC變為了MRC,我們需要手動的來管理s2的內存,而對于s1,我們即使將其置為nil,也不能釋放內存。

NSString?*s1?=?[[NSString?alloc]?initWithFormat:@"Hello,?%@!",?name];

?CFStringRef?s2?=?(CFStringRef)CFBridgingRetain(s1);

?//?do?something?with?s2

//...

?CFRelease(s2);?//?注意要在使用結束后加這個

5.3、__bridge_transfer(修飾符)?or?CFBridgingRelease(函數)

這個修飾符和函數的功能和上面那個__bridge_retained相反,它表示將管理的責任由Core Foundation轉交給Objective-C,即將管理方式由MRC轉變為ARC。

CFStringRef?result?=?CFURLCreateStringByAddingPercentEscapes(.?.?.);

?NSString?*s?=?(__bridge_transfer?NSString?*)result;

//or?NSString?*s?=?(NSString?*)CFBridgingRelease(result);

?return?s;

這里我們將result的管理責任交給了ARC來處理,我們就不需要再顯式地將CFRelease()了。

對了,這里你可能會注意到一個細節,和ARC中那個4個主要的修飾符(__strong,__weak,...)不同,這里修飾符的位置是放在類型前面的,雖然官方文檔中沒有說明,但看官方的頭文件可以知道。小伙伴們,記得別把位置寫錯哦。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容