理解 Objective-C 的 ARC

英文原文:Understanding Automatic Reference Counting in Objective-Chtml

自動引用計數(Automatic Reference Counting, ARC)把壓在程序員們肩頭的管理內存的重擔卸除了很多,更不用說讓跟蹤內存泄漏那樣的煩心事也少了不少。不過,雖然ARC很棒,咱們仍然不能徹底把內存管理這回事兒拋在腦後。 ios

這篇文章將要討論如下方面的問題,幫助你們快速進入ARC的世界。 git

  1. 內存的引用計數: 快速複習
  2. ARC的工做原理
  3. 在工程中開啓ARC
  4. ARC施加的新規則
  5. ARC限定符 - 聲明的屬性
  6. ARC限定符 - 常規變量
  7. 移植到ARC
  8. 引入不兼容ARC的代碼
  9. 我該用ARC嗎?
 

發生了什麼事? 程序員

ARC Related changes to Xcode 4.2

在ARC出現之前,程序員們只能靠retain/relese/autorelease來確保對象們剛好「堅持」到被須要的那一刻。若是忘了retain,或者屢次release某個對象,程序就會發生內存泄漏的問題,甚至直接崩潰。 github

在Xcode 4.2中,除了語法檢查外,Apple的新LLVM編譯器還將內存管理的苦差事接了過來,它會檢查代碼,決定什麼時候釋放對象。Apple的文檔裏是這麼定義ARC的: xcode

「自動引用計數(ARC)是一個編譯器級的功能,它能簡化Cocoa應用中對象生命週期管理(內存管理)的流程。」 

ARC使內存管理在大部分時候變得如同小事一樁,但咱們仍要在決定本身的類如何管理其它對象的引用時承擔一些責任。 安全

那麼,讓咱們正式開始吧…… session


 

引用計數: 快速複習 

手工管理、引用計數式的內存管理在iOS中是這樣工做的: 當使用alloc/init(或其它相似方法)建立對象時,隨同對象返回的,還有個retainCount,其值爲1,代表咱們得到了這個對象的全部權。 app

?
1
2
3
NSObject *obj = [[NSObject alloc] init];
// do some stuff
[obj release];
在對象的alloc/init和release(即放棄對象的全部權)之間,咱們能夠任意處理它,這很安全,由於系統是沒法回收正在使用中的對象的。 

將對象加入到自動釋放池也是相似,對象會一直存在,直到將來的某個時間咱們再也不須要它,纔會被系統回收。 ide

?
1
2
3
4
-(NSObject*) someMethod {
   NSObject *obj = [[[NSObject alloc] init] autorelease];
   return obj;  // will be deallocated by autorelease pool later
}

 

ARC的工做原理 

大多數新的iOS程序員都會在引用計數這問題上遇到理解障礙。ARC則是一個編譯前的步驟,它爲咱們的代碼自動加上retain/release/autorelease語句。 

ARC並非垃圾收集,並且,引用計數也沒有消失,只是變成自動而已。聽起來像是過後追加的這麼一個功能,不過,只要咱們想想Objective-C有多少功能是經過對源文件的預處理來實現的,就不會這麼想了。 

當採用ARC後,代碼只要這樣寫: 

?
1
2
NSObject *obj = [[NSObject alloc] init];
// do some stuff
ARC會自動將它變成: 
?
1
2
3
NSObject *obj = [[NSObject alloc] init];
// do some stuff
[obj release];  // **Added by ARC**

從下圖(來自Apple官方文檔)看起來,好像retain/release的數量快遇上真正有用的代碼了。固然,這確定不是熟手的狀況,不過能夠當作是對新手的保守估計。這些代碼跑起來,要跟蹤某個內存問題真的是會搞死人。 

來源: Programming With ARC Release Notes


 

在工程中開啓ARC

若是想開啓ARC,只要在工程的Build Settings中設置ARC爲YES。在幕後,其實是設置了-fobj-arc的編譯器標識。

ARC施加的新規則

若是想用ARC,必須服從一些新規則。

1. 對象的Alloc/Init

建立對象的方法跟之前同樣,但你必定不能調用retain/release/autorelease/retainCount。也不能經過selector偷偷地調用它們: 禁止使用@selector(retain)和@selector(release)。


 

2. dealloc方法 

ARC爲自動爲你調用,必定不能直接調用dealloc。不過,若是你須要釋放實例變量之外的資源,仍是能夠建立自定義的dealloc方法。但在這個方法裏,不要調用[super dealloc]。由於ARC會幫你調。 

3. 聲明的屬性 

在ARC以前,咱們是用@property指令中的assign/retain/copy參數來告訴編譯器,如何管理這些屬性的內存。用了ARC以後,這些參數就做廢了,改用weak/strong這兩個參數。 


 

4. C結構中的對象指針 

一樣禁止使用。文檔裏建議不要把它們放在結構了,改放到類裏去。不然ARC就不認識它們了。可能會出現一些移植上的問題。不過,ARC是能夠以文件爲單位來關閉的。參考下文的「引入不兼容ARC的代碼」。 

5. id與void*之間的臨時互轉 

當咱們在Core Foundation的C函數和Foundation Kit的Objective-C方法間傳遞對象時,經常須要進行id和void*兩個類型的互轉。叫作免費橋接(Toll Free Bridging)。 

若是使用ARC,必須在CF對象進入和脫離ARC的控制時,用提示/限定符來告知編譯器。限定符有__bridge、__bridge_retain和__bridge_transfer。另外,仍須要用CFRetain和CFRelease來管理Core Foundation的對象。 

這一塊已經比較高深了,若是你不清楚CF對象是什麼,也不須要太煩惱。 

 

6. 以@autoreleasepool代替NSAutoReleasePool 

兼容ARC的代碼不能再使用NSAutoReleasePool對象,而要改用@autoreleasepool{}塊。一個很好的例子: 

?
1
2
3
4
5
6
int main( int argc,  char *argv[])
{
   @autoreleasepool {
     return UIApplicationMain(argc, argv, nil, NSStringFromClass([ExampleAppDelegate  class ]));
   }
}

7. 其它 

基於Zone的內存已經沒了(在運行時裏也沒了)。不能再使用NSAllocateObject和NSDeallocateObject。 


 

ARC限定符 - 聲明的屬性 

身爲程序員,習慣於作出一些決定,例如把某個量聲明爲變量仍是常量、本地仍是全局,等等。所以,在這裏,咱們也要決定某個屬性與其它屬性的關係。咱們用strong/weak來把這一關係告訴編譯器。 

強引用 

強引用是對某對象的引用,而且能阻止它被回收。換句話說,強引用建立了一個全部關係。在ARC以前,咱們這麼寫: 

?
1
2
// Non-ARC Compliant Declaration
@property(retain) NSObject *obj;

在ARC下,咱們須要這麼寫,以確保當前實例得到被引用對象的全部權(主人不被回收,它也不能被回收)。 

?
1
2
// ARC Compliant Declaration
@property(strong) NSObject *obj;

弱引用 

弱引用是對某對象的引用,但不能阻止它被回收。換句話說,弱引用並不會建立全部關係。在ARC以前,咱們這麼寫:

 

?
1
2
// Non-ARC Compliant Declaration
@property(assign) NSObject *parentObj;
在ARC下,咱們須要這麼寫,以確保當前實例沒有得到被引用對象的全部權(通常來講,子對象不該該擁有父對象,這時能夠用弱引用)。 
?
1
2
// ARC Compliant Declaration
@property(weak) NSObject *parentObj;

 

ARC限定符 - 常規變量

上一節是說明如何管理屬性。對於常規變量,則有:

 

?
1
2
3
4
__strong
__weak
__unsafe_unretained
__autoreleasing
通常來講,咱們不太須要使用上面這些限定符。在使用移植工具的時候可能會看到那麼幾個,但新工程基本上不須要。 
  • __strong: 默認限定符,不須要顯式指定。表示任何用alloc/init建立的對象在當前範圍的生命期內得以保留。「當前範圍」是指變量聲明語句所在的兩個大括號之間(方法、循環、塊,等等)。
  • __weak: 表示對象能夠隨時被摧毀。只有當它被其它對象強引用時纔有用。__weak變量在摧毀時,被設爲nil。
  • __unsafe_unretained: 與__weak相似,但在摧毀時,不設爲nil,保留原值(再也不指向有效的東西)。
  • __autoreleasing: 不要與autorelease搞混,它用於經過引用傳遞對象,好比,經過引用傳遞NSError對象: [myObject performOperationWithError:&tmp]。

來源http://clang.llvm.org/docs/AutomaticReferenceCounting.html#ownership

: 咱們發如今ARC下,@property中使用「retain」時(而不是「strong」),編譯器並不會報錯,並且能生成一樣結果。但之後可能會變,因此仍是用「strong」吧。 


 

移植到ARC

Xcode 4.2提供了一個移植ARC的工具,還能夠幫你將沒法自動移植的代碼手工轉換過去。

1. 打開不兼容ARC的工程,進入Edit > Refactor > Convert to Objective-C ARC。

2. 選擇須要轉換的構建目標和文件(在後續步驟排除不須要轉換的文件)

3. 運行預查,按下一步。

注: 按下一步後,LLVM編譯器會開始構建,以便對工程進行分析。若是有錯誤,是沒法進入下一步的。若是是第一次打開低版本Xcode創建的工程,請先執行清理。


 
其它翻譯版本(1)

4. 檢查一下工具建議的修改,並選擇是否要排除某個文件。而後按下保存。 

注: 若是有文件沒法移植,工具也會告訴你。並非全部文件都須要移植(包括庫)。ARC是以文件爲基礎生效的,能夠參考下文,看編譯時如何把不須要開啓ARC的文件排除在外。 

5. 工具會自動設置編譯器的標識,開啓ARC。能夠查看工程的Build Settings確認這一點。 


 

引入不兼容ARC的代碼 

Apple的文檔說,「ARC能以文件爲基礎,與採用手工引用計數的代碼進行交互。你能夠在部分文件上使用手工引用計數。」 

它的意思是說,咱們可讓一部分文件用ARC,另外一部分文件不用。下面是將文件批量排除在ARC以外的步驟。在我寫下這篇文章的時候,還有許多流行的庫都沒有用ARC,爲了解決這個問題,請按照下面的步驟作: 

  1. 在Xcode的工程樹上,點擊你本身的工程
  2. 點擊Target
  3. 選擇Build Phases標籤
  4. 展開Compile Sources
  5. 選擇須要排除在ARC外的文件
  6. 按下回車
  7. 輸入-fno-objc-arc
  8. 再按下回車
  9. 如今,你選中的文件都有了-fno-objc-arc編譯器標識,會被排除在ARC以外

 

我該用ARC嗎?

若是你是Objective-C的新手,確定會歡迎ARC。一開始,有太多的東西要學習,有了ARC就不用擔憂手工計數的問題。隨着你開始接觸一些已有的庫,就會經歷一些痛苦(譯者注: 目前的第三方庫不少不兼容ARC),但只要習慣了將它們排除在ARC以外,就沒什麼問題了。 

若是你不是新手,在沒有ARC的年代已經玩的很high,那麼可能會以爲「我幹嗎要用它!」對你來講,這多是正確的答案——就目前而言。由於,大多數流行的庫都還沒轉到ARC下,並且ARC對Core Foundation的支持也很差。使用CF類的時候有一些限制,並且,移植代碼的時候,爲了讓免費橋接生效,還須要加上一大堆限定符。 


 

在我看來,目前ARC已是可使用的狀態了。不過,除非你對它很熟悉,不然仍是先用在新工程裏會比較好。雖然ARC兼容iOS 4.0以上,但弱引用只在iOS 5.0以上才支持,因此如今仍是不要把全部東西都移植過去(有一些相關的文章,請參考最後的「資源」一節) 

至於性能方面,早前有報告指出用ARC以後速度會變快,我想多是因爲減小對自動釋放的依賴的緣故。雖然這徹底能夠經過改進代碼、更好地使用retain/release來實現,但我以爲重點在於,ARC老是會自動幫你選擇最優的方法。 


 

目前爲止,仍是有不少使人苦惱的東西被移到ARC,但還不是所有。在長週末後咱們會離開一段時間以投入到新的項目,可是這是蘋果一個「新的」推薦手段,因此你能夠確定之後的設計決策會繼續強大ARC而且讓你們離開使用手動的引用計數 

ARC資源 

 

相關文章
相關標籤/搜索