標籤:
英文原文:Understanding Automatic Reference Counting in Objective-C
自動引用計數(Automatic Reference Counting, ARC)把壓在程式員們肩頭的管理記憶體的重擔卸載了不少,更不用說讓跟蹤記憶體流失那樣的煩心事也少了很多。不過,雖然ARC很棒,我們仍然不能完全把記憶體管理這回事兒拋在腦後。 這篇文章將要討論以下方面的問題,協助大家快速進入ARC的世界。
- 記憶體的引用計數: 快速複習
- ARC的工作原理
- 在工程中開啟ARC
- ARC施加的新規則
- ARC限定符 - 聲明的屬性
- ARC限定符 - 常規變數
- 移植到ARC
- 引入不相容ARC的代碼
- 我該用ARC嗎?
|
|
發生了什麼事? 在ARC出現以前,程式員們只能靠retain/relese/autorelease來確保對象們恰好“堅持”到被需要的那一刻。如果忘了retain,或者多次release某個對象,程式就會發生記憶體流失的問題,甚至直接崩潰。 在Xcode 4.2中,除了語法檢查外,Apple的新LLVM編譯器還將記憶體管理的苦差事接了過來,它會檢查代碼,決定何時釋放對象。Apple的文檔裡是這麼定義ARC的:
“自動引用計數(ARC)是一個編譯器級的功能,它能簡化Cocoa應用中對象生命週期管理(記憶體管理)的流程。”
ARC使記憶體管理在大部分時候變得如同小事一樁,但我們仍要在決定自己的類如何管理其它對象的引用時承擔一些責任。 那麼,讓我們正式開始吧…… |
|
| |
| 引用計數: 快速複習 手工管理、引用計數式的記憶體管理在iOS中是這樣工作的: 當使用alloc/init(或其它類似方法)建立對象時,隨同對象返回的,還有個retainCount,其值為1,表明我們獲得了這個對象的所有權。 ?
| 123 |
NSObject *obj = [[NSObject alloc] init];// do some stuff[obj release]; |
在對象的alloc/init和release(即放棄對象的所有權)之間,我們可以任意處理它,這很安全,因為系統是無法回收正在使用中的對象的。 將對象加入到自動釋放池也是類似,對象會一直存在,直到未來的某個時間我們不再需要它,才會被系統回收。 ?
| 1234 |
-(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後,代碼只要這樣寫: ?
| 12 |
NSObject *obj = [[NSObject alloc] init];// do some stuff |
ARC會自動將它變成: ?
| 123 |
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{}塊。一個很好的例子: ?
| 123456 |
int main(int argc, char *argv[]){ @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([ExampleAppDelegate class])); }} |
7. 其它 基於Zone的記憶體已經沒了(在運行時裡也沒了)。不能再使用NSAllocateObject和NSDeallocateObject。 |
|
| ARC限定符 - 聲明的屬性 身為程式員,習慣於做出一些決定,例如把某個量聲明為變數還是常量、本地還是全域,等等。因此,在這裡,我們也要決定某個屬性與其它屬性的關係。我們用strong/weak來把這一關係告訴編譯器。 強引用 強引用是對某對象的引用,並且能阻止它被回收。換句話說,強引用建立了一個所有關係。在ARC之前,我們這麼寫: ?
| 12 |
// Non-ARC Compliant Declaration@property(retain) NSObject *obj; |
在ARC下,我們需要這麼寫,以確保當前執行個體獲得被引用對象的所有權(主人不被回收,它也不能被回收)。 ?
| 12 |
// ARC Compliant Declaration@property(strong) NSObject *obj; |
弱引用 弱引用是對某對象的引用,但不能阻止它被回收。換句話說,弱引用並不會建立所有關係。在ARC之前,我們這麼寫: ?
| 12 |
// Non-ARC Compliant Declaration@property(assign) NSObject *parentObj; |
在ARC下,我們需要這麼寫,以確保當前執行個體沒有獲得被引用對象的所有權(一般來說,子物件不應該擁有父物件,這時可以用弱引用)。 ?
| 12 |
// ARC Compliant Declaration@property(weak) NSObject *parentObj; |
|
|
| ARC限定符 - 常規變數 上一節是說明如何管理屬性。對於常規變數,則有: ?
| 1234 |
__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,為瞭解決這個問題,請按照下面的步驟做:
- 在Xcode的工程樹上,點擊你自己的工程
- 點擊Target
- 選擇Build Phases標籤
- 展開Compile Sources
- 選擇需要排除在ARC外的檔案
- 按下斷行符號
- 輸入-fno-objc-arc
- 再按下斷行符號
- 現在,你選中的檔案都有了-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資源
- Apple’s ARC Programming Release Notes
- Clang documentation on LLVM complier ARC
- Work around for supporting zeroing weak references in iOS 4 and OS X 10.6
- Managing Toll Free Bridging Between Objective-C & Core Foundation Objects
- WHAT is Toll Free Bridging?
- Chris Lattner’s WWDC2011 Presentation Introducing ARC (ADC Members Only)
|
理解 Objective-C 的 ARC