轉自 http://www.yifeiyang.net/development-of-the-iphone-simply-1/
新年伊始,永珍更新。新一年開始,我們來更加深入瞭解一下iPhone開發的內部。作為開始,我們先來瞭解一下ARC。
ARC是什麼
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前後的變化點。
1234567891011121314151617181920 |
@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 |
123456789101112131415 |
@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 |
-
我們之前使用Objective-C中記憶體管理規則時,往往採用下面的準則
- 產生對象時,使用autorelease
- 對象代入時,先autorelease後再retain
- 對象在函數中返回時,使用return [[object retain] autorelease];
而使用ARC後,我們可以不需要這樣做了,甚至連最基礎的release都不需要了。
使用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,如。
ARC基本規則
- retain, release, autorelease, dealloc由編譯器自動插入,不能在代碼中調用
- dealloc雖然可以被重載,但是不能調用[super dealloc]
由於ARC並不是GC,並需要一些規則讓編譯器支援代碼插入,所以必須清楚清楚了這些規則後,才能寫出健壯的代碼。
Objective-C對象
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 開始匯入。由於該類型不影響對象的生命週期,所以如果對象之前就沒有持有人,那麼會出現剛建立就被破棄的問題,比如下面的代碼。
12 |
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。他被經常用於函數有值參數返回時的處理,比如下面的例子。
1234567891011 |
- (void) generateErrorInVariable:(__autoreleasing NSError **)paramError { .... *paramError = [[NSError alloc] initWithDomain:@"MyApp" code:1 userInfo:errorDictionary];}....{ NSError *error = nil; [self generateErrorInVariable:&error]; NSLog(@"Error = %@", error);} |
又如函數的傳回值是在函數中申請的,那麼希望釋放是在調用端時,往往有下面的代碼。
123456789101112131415 |
-(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,學習其更多的特性。