iOS應(yīng)用開(kāi)發(fā):什么是ARC?
新年伊始,萬(wàn)象更新。新一年開(kāi)始,我們來(lái)更加深入了解一下iPhone開(kāi)發(fā)的內(nèi)部。作為開(kāi)始,我們先來(lái)了解一下ARC。
ARC是iOS 5推出的新功能,全稱叫 ARC(Automatic Reference Counting)。簡(jiǎn)單地說(shuō),就是代碼中自動(dòng)加入了retain/release,原先需要手動(dòng)添加的用來(lái)處理內(nèi)存管理的引用計(jì)數(shù)的代碼可以自動(dòng)地由編譯器完成了。
該機(jī)能在 iOS 5/ Mac OS X 10.7 開(kāi)始導(dǎo)入,利用 Xcode4.2 可以使用該機(jī)能。簡(jiǎn)單地理解ARC,就是通過(guò)指定的語(yǔ)法,讓編譯器(LLVM 3.0)在編譯代碼時(shí),自動(dòng)生成實(shí)例的引用計(jì)數(shù)管理部分代碼。有一點(diǎn),ARC并不是GC,它只是一種代碼靜態(tài)分析(Static Analyzer)工具。
通過(guò)一小段代碼,我們看看使用ARC前后的變化點(diǎn)。
- @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
- 生成對(duì)象時(shí),使用autorelease
- 對(duì)象代入時(shí),先autorelease后再retain
- 對(duì)象在函數(shù)中返回時(shí),使用return [[object retain] autorelease];
而使用ARC后,我們可以不需要這樣做了,甚至連最基礎(chǔ)的release都不需要了。
使用ARC有什么好處呢?
- 看到上面的例子,大家就知道了,以后寫Objective-C的代碼變得簡(jiǎn)單多了,因?yàn)槲覀儾恍枰獡?dān)心煩人的內(nèi)存管理,擔(dān)心內(nèi)存泄露了
- 代碼的總量變少了,看上去清爽了不少,也節(jié)省了勞動(dòng)力
- 代碼高速化,由于使用編譯器管理引用計(jì)數(shù),減少了低效代碼的可能性
- 記住一堆新的ARC規(guī)則 — 關(guān)鍵字及特性等需要一定的學(xué)習(xí)周期
- 一些舊的代碼,第三方代碼使用的時(shí)候比較麻煩;修改代碼需要工數(shù),要么修改編譯開(kāi)關(guān)
關(guān)于第二點(diǎn),由于 XCode4.2 中缺省ARC就是 ON 的狀態(tài),所以編譯舊代碼的時(shí)候往往有"Automatic Reference Counting Issue"的錯(cuò)誤信息。

這個(gè)時(shí)候,可以將項(xiàng)目編譯設(shè)置中的“Objectice-C Auto Reference Counteting”設(shè)為NO。如下所示。

如果只想對(duì)某個(gè).m文件不適應(yīng)ARC,可以只針對(duì)該類文件加上 -fno-objc-arc 編譯FLAGS,如下圖。

- retain, release, autorelease, dealloc由編譯器自動(dòng)插入,不能在代碼中調(diào)用
- dealloc雖然可以被重載,但是不能調(diào)用[super dealloc]
由于ARC并不是GC,并需要一些規(guī)則讓編譯器支持代碼插入,所以必須清楚清楚了這些規(guī)則后,才能寫出健壯的代碼。
ObjectiveC中的對(duì)象,有強(qiáng)參照(Strong reference)和弱參照(Weak reference)之分,當(dāng)需要保持其他對(duì)象的時(shí)候,需要retain以確保對(duì)象引用計(jì)數(shù)加1。對(duì)象的持有者(owner)只要存在,那么該對(duì)象的強(qiáng)參照就一直存在。
- 對(duì)象處理的基本規(guī)則是
- 只要對(duì)象的持有者存在(對(duì)象被強(qiáng)參照),那么就可以使用該對(duì)象
- 對(duì)象失去了持有者后,即被破棄
強(qiáng)參照 (Strong reference)
(s1)
firstName作為”natsu”字符串對(duì)象的最初持有者,是該NSString類型對(duì)象的Strong reference。
- (s2)
這里將firstName代入到aName中,即aName也成為了@”natsu”字符串對(duì)象的持有者,對(duì)于該對(duì)象,aName也是Strong reference。
- (s3)
這里,改變firstName的內(nèi)容。生成新的字符串對(duì)象”maki”。這時(shí)候firstName成為”maki”的持有者,而@”natsu”的持有者只有aName。每個(gè)字符串對(duì)象都有各自的持有者,所以它們都在內(nèi)存中都存在。
- (s4)
追加新的變量otherName, 它將成為@”maki”對(duì)象的另一個(gè)持有者。即NSString類型對(duì)象的Strong reference。
- (s5)
將otherName代入到aName,這時(shí),aName將成為@”maki”字符串對(duì)象的持有者。而對(duì)象@”natsu”已經(jīng)沒(méi)有持有者了,該對(duì)象將被破棄。
弱參照 (Weak reference)
接下來(lái)我們來(lái)看看弱參照 (Weak reference) 的使用方式。

(w1)
與強(qiáng)參照方式同樣,firstName作為字符串對(duì)象@”natsu”的持有者存在。即是該NSString類型對(duì)象的Strong reference。
- (w2)
使用關(guān)鍵字__weak,聲明弱參照weakName變量,將firstName代入。這時(shí)weakName雖然參照@”natsu”,但仍是Weak reference。即weakName雖然能看到@”natsu”,但不是其持有者。
- (w3)
firstName指向了新的對(duì)象@”maki”,成為其持有者,而對(duì)象@”natsu”因?yàn)闆](méi)有了持有者,即被破棄。同時(shí)weakName變量將被自動(dòng)代入nil。
ARC中關(guān)于對(duì)象的引用參照,主要有下面幾關(guān)鍵字。使用strong, weak, autoreleasing限定的變量會(huì)被隱式初始化為nil。
- __strong
變量聲明缺省都帶有__strong關(guān)鍵字,如果變量什么關(guān)鍵字都不寫,那么缺省就是強(qiáng)參照。
- __weak
上面已經(jīng)看到了,這是弱參照的關(guān)鍵字。該概念是新特性,從 iOS 5/ Mac OS X 10.7 開(kāi)始導(dǎo)入。由于該類型不影響對(duì)象的生命周期,所以如果對(duì)象之前就沒(méi)有持有者,那么會(huì)出現(xiàn)剛創(chuàng)建就被破棄的問(wèn)題,比如下面的代碼。
- NSString __weak *string = [[NSString alloc] initWithFormat:@"First Name: %@", [self firstName]];
- NSLog(@"string: %@", string); //此時(shí) string為空
如果編譯設(shè)定OS版本 Deployment Target 設(shè)定為這比這低的版本,那么編譯時(shí)將報(bào)錯(cuò)(The current deployment target does not support automated __weak references),這個(gè)時(shí)候,我們可以使用下面的__unsafe_unretained。
弱參照還有一個(gè)特征,即當(dāng)參數(shù)對(duì)象失去所有者之后,變量會(huì)被自動(dòng)付上nil (Zeroing)。
- __unsafe_unretained
該關(guān)鍵字與__weak一樣,也是弱參照,與__weak的區(qū)別只是是否執(zhí)行nil賦值(Zeroing)。但是這樣,需要注意變量所指的對(duì)象已經(jīng)被破棄了,地址還還存在,但內(nèi)存中對(duì)象已經(jīng)沒(méi)有了。如果還是訪問(wèn)該對(duì)象,將引起「BAD_ACCESS」錯(cuò)誤。
- __autoreleasing
該關(guān)鍵字使對(duì)像延遲釋放。比如你想傳一個(gè)未初始化的對(duì)像引用到一個(gè)方法當(dāng)中,在此方法中實(shí)例化此對(duì)像,那么這種情況可以使用__autoreleasing。他被經(jīng)常用于函數(shù)有值參數(shù)返回時(shí)的處理,比如下面的例子。
- - (void) generateErrorInVariable:(__autoreleasing NSError **)paramError {
- ....
- *paramError = [[NSError alloc] initWithDomain:@"MyApp" code:1 userInfo:errorDictionary];
- }
- ....
- {
- NSError *error = nil;
- [self generateErrorInVariable:&error];
- NSLog(@"Error = %@", error);
- }
又如函數(shù)的返回值是在函數(shù)中申請(qǐng)的,那么希望釋放是在調(diào)用端時(shí),往往有下面的代碼。
- -(NSString *)stringTest
- {
- NSString *retStr = [NSString stringWithString:@"test"];
- return [[retStr retain] autorelease];
- }
- // 使用ARC
- -(NSString *)stringTest
- {
- __autoreleasing NSString *retStr = [NSString alloc] initWithString:@"test"];
- return retStr;
- }
即當(dāng)方法的參數(shù)是id*,且希望方法返回時(shí)對(duì)象被autoreleased,那么使用該關(guān)鍵字。
- 今天,我們看到了基本的ARC使用規(guī)則
- 代碼中不能使用retain, release, retain, autorelease
- 不重載dealloc(如果是釋放對(duì)象內(nèi)存以外的處理,是可以重載該函數(shù)的,但是不能調(diào)用[super dealloc])
- 不能使用NSAllocateObject, NSDeallocateObject
- 不能在C結(jié)構(gòu)體中使用對(duì)象指針
- id與void *間的如果cast時(shí)需要用特定的方法(__bridge關(guān)鍵字)
- 不能使用NSAutoReleasePool、而需要@autoreleasepool塊
- 不能使用“new”開(kāi)始的屬性名稱 (如果使用會(huì)有下面的編譯錯(cuò)誤”Property’s synthesized getter follows Cocoa naming convention for returning ‘owned’ objects”)
原文地址:http://www.yifeiyang.net/development-of-the-iphone-simply-1/