ARC是iOS 5推出的新功能,全稱叫 ARC(Automatic Reference Counting)。簡單地說,就是代碼中自動加入了retain/release,原先需要手動添加的用來處理內存管理的引用計數的代碼可以自動地由編譯器完成了。
該機能在 iOS 5/ Mac OS X 10.7 開始導入,利用 Xcode4.2 可以使用該機能。簡單地理解ARC,就是通過指定的語法,讓編譯器(LLVM 3.0)在編譯代碼時,自動生成實例的引用計數管理部分代碼。有一點,ARC並不是GC,它只是一種代碼靜態分析(Static Analyzer)工具。
通過一小段代碼,我們看看使用ARC前后的變化點。
- @interface NonARCObject : NSObject {
- NSString *name;
- }
- -(id)initWithName:(NSString *)name;
- @end
- @implementation NonARCObject
- -(id)initWithName:(NSString *)newName {
- self = [super init];
- if (self) {
- name = [newName retain];
- }
- return self;
- }
- -(void)dealloc {
- [name release];
- [Super dealloc];
- }
- @end
- @interface ARCObject : NSObject {
- NSString *name;
- }
- -(id)initWithName:(NSString *)name;
- @end
- @implementation ARCObject
- -(id)initWithName:(NSString *)newName {
- self = [super init];
- if (self) {
- name = newName;
- }
- return self;
- }
- @end
- 生成對象時,使用autorelease
- 對象代入時,先autorelease后再retain
- 對象在函數中返回時,使用return [[object retain] autorelease];
而使用ARC后,我們可以不需要這樣做了,甚至連最基礎的release都不需要了。
| 說明:在arc中,strong對應原來的retain與copy,weak對應原來的assign。
arc的使用有兩點: A:在build phases中修改compiler Flags值。 B:在代碼中判斷是否支持arc,包括對屬性(property)、釋放(release)的判斷。
在dealloc中需要這樣做: 類如果注冊了通知(觀察者模式),需要remove掉。這個不管是否支持arc,都必須要做的。 - (void)dealloc { [[NSNotificationCenterdefaultCenter] removeObserver:self];//如果注冊了通知的話。 [self removeObserver:self forKeyPath:keyPath];//如果注冊了kvo的話。 #if !__has_feature(objc_arc) //在這里也需要判斷是否支持arc,支持的話就執行舊工程中該release的語句. [array release]; //array代表alloc但沒有autorelease的變量 [super dealloc]; #endif }
總結:
1,arc的設置是在build phases中修改compiler Flags的值。
2,如果使用了arc,在你的代碼中不可以使用retain, release, autorelease,如果使用的話會報錯。
3,如果使用了arc,在@property聲明中,用strong和weak代替相應的retain, copy,和assign。
4,如果使用了arc,NSAutoReleasePool也不能使用,測試發現,用@autoreleasepool 代替,不會編譯報錯。
總之,一切你之前“背過”的那幾條內存管理規則,你都不用去管了。而且,個人感覺,用arc代碼清晰很多,而且效率也提高了些。
|
使用ARC有什么好處呢?
- 看到上面的例子,大家就知道了,以后寫Objective-C的代碼變得簡單多了,因為我們不需要擔心煩人的內存管理,擔心內存泄露了
- 代碼的總量變少了,看上去清爽了不少,也節省了勞動力
- 代碼高速化,由於使用編譯器管理引用計數,減少了低效代碼的可能性
- 記住一堆新的ARC規則 — 關鍵字及特性等需要一定的學習周期
- 一些舊的代碼,第三方代碼使用的時候比較麻煩;修改代碼需要工數,要么修改編譯開關
關於第二點,由於 XCode4.2 中缺省ARC就是 ON 的狀態,所以編譯舊代碼的時候往往有"Automatic Reference Counting Issue"的錯誤信息。
這個時候,可以將項目編譯設置中的“Objectice-C Auto Reference Counteting”設為NO。如下所示。
如果只想對某個.m文件不適應ARC,可以只針對該類文件加上 -fno-objc-arc 編譯FLAGS,如下圖。
- retain, release, autorelease, dealloc由編譯器自動插入,不能在代碼中調用
- dealloc雖然可以被重載,但是不能調用[super dealloc]
由於ARC並不是GC,並需要一些規則讓編譯器支持代碼插入,所以必須清楚清楚了這些規則后,才能寫出健壯的代碼。
ObjectiveC中的對象,有強參照(Strong reference)和弱參照(Weak reference)之分,當需要保持其他對象的時候,需要retain以確保對象引用計數加1。對象的持有者(owner)只要存在,那么該對象的強參照就一直存在。
對象處理的基本規則是- 只要對象的持有者存在(對象被強參照),那么就可以使用該對象
- 對象失去了持有者后,即被破棄
強參照 (Strong reference)
(s1)
firstName作為”natsu”字符串對象的最初持有者,是該NSString類型對象的Strong reference。
(s2)這里將firstName代入到aName中,即aName也成為了@”natsu”字符串對象的持有者,對於該對象,aName也是Strong reference。
(s3)這里,改變firstName的內容。生成新的字符串對象”maki”。這時候firstName成為”maki”的持有者,而@”natsu”的持有者只有aName。每個字符串對象都有各自的持有者,所以它們都在內存中都存在。
(s4)追加新的變量otherName, 它將成為@”maki”對象的另一個持有者。即NSString類型對象的Strong reference。
(s5)將otherName代入到aName,這時,aName將成為@”maki”字符串對象的持有者。而對象@”natsu”已經沒有持有者了,該對象將被破棄。
弱參照 (Weak reference)
接下來我們來看看弱參照 (Weak reference) 的使用方式。
(w1)
與強參照方式同樣,firstName作為字符串對象@”natsu”的持有者存在。即是該NSString類型對象的Strong reference。
(w2)使用關鍵字__weak,聲明弱參照weakName變量,將firstName代入。這時weakName雖然參照@”natsu”,但仍是Weak reference。即weakName雖然能看到@”natsu”,但不是其持有者。
(w3)firstName指向了新的對象@”maki”,成為其持有者,而對象@”natsu”因為沒有了持有者,即被破棄。同時weakName變量將被自動代入nil。
ARC中關於對象的引用參照,主要有下面幾關鍵字。使用strong, weak, autoreleasing限定的變量會被隱式初始化為nil。
- __strong
變量聲明缺省都帶有__strong關鍵字,如果變量什么關鍵字都不寫,那么缺省就是強參照。
- __weak
上面已經看到了,這是弱參照的關鍵字。該概念是新特性,從 iOS 5/ Mac OS X 10.7 開始導入。由於該類型不影響對象的生命周期,所以如果對象之前就沒有持有者,那么會出現剛創建就被破棄的問題,比如下面的代碼。
- NSString __weak *string = [[NSString alloc] initWithFormat:@"First Name: %@", [self firstName]];
- NSLog(@"string: %@", string); //此時 string為空
如果編譯設定OS版本 Deployment Target 設定為這比這低的版本,那么編譯時將報錯(The current deployment target does not support automated __weak references),這個時候,我們可以使用下面的 __unsafe_unretained。
弱參照還有一個特征,即當參數對象失去所有者之后,變量會被自動付上nil (Zeroing)。
- __unsafe_unretained
該關鍵字與__weak一樣,也是弱參照,與__weak的區別只是是否執行nil賦值(Zeroing)。但是這樣,需要注意變量所指的對象已經被破棄了,地址還還存在,但內存中對象已經沒有了。如果還是訪問該對象,將引起「BAD_ACCESS」錯誤。
- __autoreleasing
該關鍵字使對像延遲釋放。比如你想傳一個未初始化的對像引用到一個方法當中,在此方法中實例化此對像,那么這種情況可以使用__autoreleasing。他被經常用於函數有值參數返回時的處理,比如下面的例子。
- - (void) generateErrorInVariable:(__autoreleasing NSError **)paramError {
- ....
- *paramError = [[NSError alloc] initWithDomain:@"MyApp" code:1 userInfo:errorDictionary];
- }
- ....
- {
- NSError *error = nil;
- [self generateErrorInVariable:&error];
- NSLog(@"Error = %@", error);
- }
又如函數的返回值是在函數中申請的,那么希望釋放是在調用端時,往往有下面的代碼。
- -(NSString *)stringTest
- {
- NSString *retStr = [NSString stringWithString:@"test"];
- return [[retStr retain] autorelease];
- }
- // 使用ARC
- -(NSString *)stringTest
- {
- __autoreleasing NSString *retStr = [NSString alloc] initWithString:@"test"];
- return retStr;
- }
即當方法的參數是id*,且希望方法返回時對象被autoreleased,那么使用該關鍵字。
今天,我們看到了基本的ARC使用規則
- 代碼中不能使用retain, release, retain, autorelease
- 不重載dealloc(如果是釋放對象內存以外的處理,是可以重載該函數的,但是不能調用[super dealloc])
- 不能使用NSAllocateObject, NSDeallocateObject
- 不能在C結構體中使用對象指針
- id與void *間的如果cast時需要用特定的方法(__bridge關鍵字)
- 不能使用NSAutoReleasePool、而需要@autoreleasepool塊
- 不能使用“new”開始的屬性名稱 (如果使用會有下面的編譯錯誤”Property’s synthesized getter follows Cocoa naming convention for returning ‘owned’ objects”)
ARC工作原理是在編譯程序的時候由xCode將內存操作的代碼(如:retain,release 和 autorelease)自動添加到需要的位置。
ARC 只能在iOS4 和iOS5上使用,weak refrences 只能在iOS5上使用,並且只能是工程在ARC管理內存的時候才能用。
老版本的工程是可以轉換成使用ARC的工程,轉換規則包括:
1.去掉所有的retain,release,autorelease
2.把NSAutoRelease替換成@autoreleasepool{}塊
3.把assign的屬性變為weak
使用ARC的一些強制規定
1.不能直接調用dealloc方法,不能調用retain,release,autorelease,reraubCount方法,包括@selector(retain)的方式也不行
2.截圖租戶事故宣布dealloc方法來管理一些資源,但不能用來釋放實例變量,也不能在dealloc方法里面去掉[super dealloc]方法,在ARC下父類的dealloc同樣由編譯器來自動完成
3.Core Foundation類型的對象任然可以用CFRetain,CFRelease這些方法
4.不能在使用NSAllocateObject和NSDeallocateObject對象
5.不能在c結構體中使用對象指針,如果有類似功能可以創建一個Objective-c類來管理這些對象
6.在id和void *之間沒有簡便的轉換方法,同樣在Objective-c和core Foundation類型之間的轉換都需要使用編譯器制定的轉換函數
7.不能再使用NSAutoreleasePool對象,ARC提供了@autoreleasepool塊來代替它,這樣更加有效率
8.不能使用內存存儲區(不能再使用NSZone)
9.不能以new為開頭給一個屬性命名
10.聲明outlet時一般應當使用weak,除了對StoryBoard 這樣nib中間的頂層對象要用strong
11.weak 相當於老版本的assign,strong相當於retain
對工程中的單個文件制定不使用ARC的方法:在targets的build phases選項下Compile Sources下選擇要不使用arc編譯的文件,雙擊它,輸入-fno-objc-arc即可
| 屬性值 | 關鍵字 | 所有權 |
|---|---|---|
| strong | __strong | 有 |
| weak | __weak | 無 |
| unsafe_unretained | __unsafe_unretained | 無 |
| copy | __strong | 有 |
| assign | __unsafe_unretained | 無 |
| retain | __strong | 有 |
strong
該屬性值對應 __strong 關鍵字,即該屬性所聲明的變量將成為對象的持有者。
weak
該屬性對應 __weak 關鍵字,與 __weak 定義的變量一致,該屬性所聲明的變量將沒有對象的所有權,並且當對象被破棄之后,對象將被自動賦值nil。
並且,delegate 和 Outlet 應該用 weak 屬性來聲明。同時,如上一回介紹的 iOS 5 之前的版本是沒有 __weak 關鍵字的,所以 weak 屬性是不能使用的。這種情況我們使用 unsafe_unretained。
unsafe_unretained
等效於__unsafe_unretaind關鍵字聲明的變量;像上面說明的,iOS 5之前的系統用該屬性代替 weak 來使用。
copy
與 strong 的區別是聲明變量是拷貝對象的持有者。
assign
一般Scalar Varible用該屬性聲明,比如,int, BOOL。
retain
該屬性與 strong 一致;只是可讀性更強一些。
讀寫相關的屬性 (readwrite, readonly)
讀寫相關的屬性有 readwrite 和 readonly 兩種,如果使用ARC之后,我么需要注意一下 readonly 屬性的使用。
比如下面的變量聲明。
- @property (nonatomic, readonly) NSString *name;
一般聲明為 readonly 的變量按理說應該不需要持有所有權了,但是在ARC有效的情況下,將出現下面的錯誤信息 :
| “ARC forbids synthesizing a property of an Objective-C object with unspecified ownership or storage attribute |
如果定義了ARC有效,那么必須要有所有者屬性的定義;所以我們的代碼改成這樣,就OK了
- @property (nonatomic, strong, readonly) NSString *name;
不過有一點,Scalar Varible的變量缺省都有 assign 的屬性定義,所以不需要給他們單獨的明示聲明了。

