iOS 5 中最具顛覆性的變化是增加了自動引用計數(shù)(Automatic Reference Counting)晴玖,簡稱 ARC。 ARC 是新的 LLVM 3.0 編譯器的一個特性,它完全消除了所有 iOS 開發(fā)人員都因愛生恨的手動內(nèi)存管理袱衷。
在自己的項目中使用 ARC 非常簡單。 除了不再調(diào)用 retain致燥,release 和 autorelease 之外,你可以像往常一樣繼續(xù)編程。 這基本上就是 ARC 的全部了辐益。
啟用自動引用計數(shù)功能后,編譯器會自動在程序的正確位置插入 retain智政,release 和 autorelease认罩。 你不必再為此擔(dān)心,因為編譯器會為你做续捂。 這讓我很驚喜垦垂。 實際上,使用 ARC 簡單到你現(xiàn)在可以停止閱讀本教程牙瓢。
但是劫拗,如果你仍然對 ARC 持懷疑態(tài)度,也許你不相信它總是正確矾克,或者你認(rèn)為它會比手動內(nèi)存管理慢一些 —— 那么繼續(xù)讀下去页慷。 本教程的其余部分將驅(qū)散這些迷霧,并向你展示如何處理在項目中啟用 ARC 的一些不那么直觀的后果聂渊。
另外差购,我們將為你提供將不使用 ARC 的應(yīng)用程序轉(zhuǎn)換為使用 ARC 的實踐經(jīng)驗址儒。 你可以使用這些相同的技術(shù)將你現(xiàn)有的 iOS 項目轉(zhuǎn)換為使用 ARC棋蚌,從而節(jié)省大量內(nèi)存麻煩摹恨!
運行原理
你可能已經(jīng)熟悉了手動內(nèi)存管理秕衙,基本上這樣工作:
如果你需要持有一個對象帜乞,你需要保留(retain)它期虾,除非它已經(jīng)被你保留酝润。
如果你不再使用一個對象梳虽,你需要釋放(release)它弓叛,除非它已經(jīng)被你釋放(使用autorelease)彰居。
作為一個初學(xué)者,你可能會有一段艱難的時間撰筷,期間這個概念一直縈繞在腦海陈惰,但過了一段時間后,它變成了第二天性毕籽,現(xiàn)在你總能正確地用 release 來平衡你的 retain抬闯。 除非你忘了這樣做。
手動內(nèi)存管理的原則并不難关筒,但很容易犯錯誤溶握。 而這些小錯誤可能會有可怕的后果蒸播。 要么你的應(yīng)用程序在某個時候會崩潰,因為你將一個對象釋放了太多次胀屿,你的變量指向的數(shù)據(jù)不再有效碉纳,要么你的內(nèi)存耗盡劳曹,因為你沒有充分釋放對象铁孵, 它們永遠(yuǎn)存在下去蜕劝。
Xcode 的靜態(tài)分析器對于發(fā)現(xiàn)這些問題非常有幫助岖沛,但 ARC 更進一步。 它通過自動為你插入適當(dāng)?shù)?retain 和 release 來完全避免內(nèi)存管理問題廊镜!
認(rèn)識到 ARC 是 Objective-C 編譯器的一個特性是非常重要的嗤朴,因此當(dāng)你構(gòu)建你的應(yīng)用程序時雹姊,所有的 ARC 相關(guān)的都會發(fā)生吱雏。 ARC 不是一個運行時特性(除了一小部分歧杏,就是弱指針系統(tǒng)),也不是從其他語言中了解的垃圾回收(garbage collection)盒犹。
所有 ARC 所做的只是在編譯時插入 retain 和 release 到你的代碼中急膀,就在你會自己添加它們的地方 —— 或者至少是你應(yīng)該添加它們的地方卓嫂。 這使得 ARC 與手動托管代碼一樣快,有時甚至更快行瑞,因為它可以私下執(zhí)行某些優(yōu)化血久。
指針保存對象存在
你需要學(xué)習(xí)的 ARC 新規(guī)則非常簡單氧吐。 通過手動內(nèi)存管理筑舅,你需要保留一個對象以保持存在翠拣。 這不再是必要的心剥,你只需要創(chuàng)建一個指向?qū)ο蟮闹羔槨?只要有一個指向?qū)ο蟮淖兞坑派眨搶ο缶屯A粼趦?nèi)存中畦娄。 當(dāng)指針獲取新的值或不再存在時熙卡,關(guān)聯(lián)的對象被釋放驳癌。 所有變量都是如此:成員變量颓鲜,合成屬性,甚至局部變量乐严。
從所有權(quán)的角度來看這是有道理的昂验。 當(dāng)你做到下面的事情時既琴,
NSString *firstName = self.textField.text;
firstName 變量成為指向文本框內(nèi)容中的 NSString 對象的指針呛梆。 firstName 變量現(xiàn)在是該字符串對象的所有者填物。
一個對象可以有多個所有者滞磺。 在用戶更改 UITextField 的內(nèi)容之前击困,它的 text 屬性也是字符串對象的所有者阅茶。 有兩個指針保持相同的字符串對象存在:
過了一會兒脸哀,用戶將在文本框中輸入新的東西撞蜂,它的 text 屬性現(xiàn)在指向一個新的字符串對象蝌诡。 但原來的字符串對象仍然擁有一個擁有者(firstName 變量)浦旱,因此保留在內(nèi)存中九杂。
只有當(dāng) firstName 獲得一個新的值,或者超出作用域 —— 因為它是一個局部變量而方法結(jié)束了,或者因為它是一個成員變量而它所屬的對象被釋放了 —— 所有權(quán)才會過期裳擎。 字符串對象不再擁有任何所有者鹿响,其保留計數(shù)將降至 0,并且對象被釋放妈倔。
我們將諸如 firstName 和 textField.text 之類的指針稱為“強”指針盯蝴,因為它們保持對象存在捧挺。 默認(rèn)情況下闽烙,所有成員變量和局部變量都是強指針黑竞。
同樣也存在“弱”指針疏旨。 弱指針變量仍然可以指向?qū)ο蟪涫鼈儾粫蔀樗姓撸?/p>
__weak NSString *weakName = self.textField.text;
weakName 變量指向和 textField.text 屬性指向的同一個字符串對象骤铃,但它不是所有者惰爬。如果文本框的內(nèi)容發(fā)生變化撕瞧,那么字符串對象不再擁有任何所有者并被釋放:
發(fā)生這種情況時,weakName 的值自動變?yōu)?nil巩掺。即所謂“歸零”弱指針胖替。
注意這個特性非常方便独令,因為它可以防止弱指針指向已被釋放的內(nèi)存。這類事情曾經(jīng)造成很多漏洞 —— 你可能聽說過“野指針”或“僵尸” —— 但是由于這些歸零的弱指針冲呢,這已經(jīng)不再是問題敬拓!
你可能不會頻繁使用弱指針恩尾。當(dāng)兩個對象具有父子關(guān)系時翰意,它們最有用冀偶。父類會有指向子類的強指針 —— 因此“擁有”孩子 —— 但為了防止所有權(quán)循環(huán)进鸠,子類只有指向父類的弱指針形病。
委托模式是一個例子漠吻。你的視圖控制器可以通過強指針擁有一個 UITableView。表格視圖的數(shù)據(jù)源和委托指針指回視圖控制器绍傲,但是是弱指針。我們稍后會詳細(xì)討論猎塞。
注意以下代碼并不是很有用:
__weak NSString *str = [[NSString alloc] initWithFormat:...];
NSLog(@"%@", str); // will output "(null)"
字符串對象沒有所有者(因為 str 是弱指針)荠耽,所以該對象在創(chuàng)建后立刻被釋放骇塘。當(dāng)你這樣做時,Xcode 會發(fā)出警告群凶,因為它可能不是你所期望發(fā)生的事情("Warning: assigning retained object to weak variable; object will be released after assignment")请梢。
你可以使用 __strong 關(guān)鍵字來表示變量是一個強指針:
__strong NSString *firstName = self.textField.text;
但是因為默認(rèn)情況下變量是強指針毅弧,這就有點多余了够坐。
屬性也可以是強屬性和弱屬性元咙。屬性的寫法如下:
@property (nonatomic, strong) NSString *firstName;
@property (nonatomic, weak) id <MyDelegate> delegate;
ARC 非常棒庶香,它會消除大量混亂的代碼赶掖。你不再需要考慮何時 retain 以及何時 release奢赂,而只需考慮對象如何相互關(guān)聯(lián)呈驶。你需要問自己的問題是:誰擁有什么袖瞻?
例如,以前不可能編寫這樣的代碼:
id obj = [array objectAtIndex:0];
[array removeObjectAtIndex:0];
NSLog(@"%@", obj);
在手動內(nèi)存管理下脂矫,從數(shù)組中刪除對象將使 obj 變量的內(nèi)容無效庭再。只要不再是數(shù)組的一部分牺堰,該對象就會被釋放恨搓。使用 NSLog() 打印對象可能會導(dǎo)致應(yīng)用程序崩潰筏养。在 ARC 中辉浦,上面的代碼按預(yù)期工作茎辐。因為我們把對象放入 obj 變量中宪郊,這是一個強指針,數(shù)組不再是該對象的唯一所有者拖陆。即使我們從數(shù)組中刪除對象废膘,該對象仍然存在,因為 obj 始終指向它慕蔚。
自動引用計數(shù)也有一些限制丐黄。對于初學(xué)者,ARC 僅適用于 Objective-C 對象孔飒。如果你的應(yīng)用程序使用 Core Foundation 或者 malloc() 和 free() 灌闺,那么你仍然有責(zé)任對其進行內(nèi)存管理。我們將在后面的教程中看到這個例子坏瞄。此外桂对,某些語言規(guī)則會更加嚴(yán)格,以確保 ARC 能夠始終正確地完成其工作机错。這些只是小小的犧牲璧亮,你獲得的好處比你放棄的要多得多!
僅僅因為 ARC 負(fù)責(zé)在適當(dāng)?shù)牡胤綖槟?retain 和 release,并不意味著你可以完全忘記內(nèi)存管理淹禾。因為強指針使對象保持存在,所以有些情況下仍然需要手動將這些指針設(shè)置為 nil,否則應(yīng)用程序可能耗盡可用內(nèi)存。如果你一直保留你所創(chuàng)建的對象,那么 ARC 將永遠(yuǎn)無法釋放它們。因此,無論何時創(chuàng)建新對象感凤,仍然需要考慮誰擁有該對象以及該對象應(yīng)該存在多長時間萨惑。
毫無疑問贮匕,ARC 是 Objective-C 的未來。Apple 鼓勵開發(fā)人員拋棄手動內(nèi)存管理馒疹,并開始使用 ARC 編寫新應(yīng)用程序腥刹。它能保證更簡潔的代碼和更健壯的應(yīng)用程序朽色。使用 ARC梢褐,與內(nèi)存相關(guān)的崩潰已成為歷史。
但是因為我們正在進入從手動到自動內(nèi)存管理的過濾階段,所以你經(jīng)常會遇到與 ARC 不兼容的代碼,無論是在你自己的代碼中還是第三方庫。幸運的是,你可以在同一個項目中將 ARC 與非 ARC 代碼結(jié)合使用稻艰,我將向你展示幾種方法用狱。
ARC 甚至可以很好地與 C++ 結(jié)合。除了一些限制外咏连,你也可以在 iOS 4 上使用 ARC骑晶,這應(yīng)該有助于加速對此的接受碟婆。
一個聰明的開發(fā)人員會盡可能地自動化完成工作妓布,這正是 ARC 所提供的:自動完成你以前必須手動完成的編程瑣事倦沧。對我來說,切換到 ARC 是想都不用想的酝惧。
應(yīng)用程序
為了演示如何在實踐中使用自動引用計數(shù)萌踱,我準(zhǔn)備了一個簡單的應(yīng)用程序弯汰,我們將從手動內(nèi)存管理轉(zhuǎn)換為 ARC据某。應(yīng)用程序藝術(shù)家(Artists)包含具有表格視圖和搜索欄的單個屏幕才避。當(dāng)你在搜索欄中輸入內(nèi)容時,該應(yīng)用會調(diào)用 MusicBrainz API 搜索名稱匹配的音樂人糙俗。
應(yīng)用看起來像這樣:
用他們自己的話來說顶猜,MusicBrainz 是“一本開放的音樂百科全書长窄,它收集音樂數(shù)據(jù)并向公眾開放”矗愧。他們有一個免費的 XML 網(wǎng)絡(luò)服務(wù),你可以在你的應(yīng)用程序中用到它。要了解有關(guān) MusicBrainz 的更多信息馁蒂,請訪問 http://musicbrainz.org 查看他們的網(wǎng)站。
你可以在本章的文件夾中找到藝術(shù)家應(yīng)用程序的初始代碼倘潜。該項目包含以下源文件:
AppDelegate.h/.m:應(yīng)用程序委托绷柒。沒什么特別的,每個應(yīng)用都有一個涮因。它加載視圖控制器并將其放入窗口中废睦。
MainViewController.h/.m/.xib:應(yīng)用程序的視圖控制器。它有一個表格視圖和一個搜索欄养泡,并且完成大部分工作嗜湃。
SoundEffect.h/.m:一個用于播放聲音效果的簡單類。當(dāng) MusicBrainz 搜索完成時澜掩,該應(yīng)用程序會發(fā)出嘟嘟聲购披。
main.m:應(yīng)用程序的入口點。
另外肩榕,該應(yīng)用程序使用兩個第三方庫刚陡。你的應(yīng)用程序可能會使用一些自己的外部組件,學(xué)習(xí)如何使這些庫同 ARC 和諧共存是有益的株汉。
AFHTTPRequestOperation.h/.m:AFNetworking 庫的一部分筐乳,可以輕松地執(zhí)行對Web服務(wù)的請求。我沒有包含完整的庫乔妈,因為我們只需要這一個類蝙云。你可以在 https://github.com/gowalla/AFNetworking 找到完整的軟件包。
SVProgresHUD.h/.m/.bundle:一個在搜索過程中顯示于屏幕上的進度指示器路召。你可能以前沒有見過 .bundle 文件勃刨。這是一種特殊類型的文件夾,其中包含 SVProgressHUD 使用的圖像文件股淡。要查看這些圖像身隐,請右鍵單擊 Finder 中的 .bundle 文件,然后選擇 Show Package Contents 菜單選項揣非。有關(guān)此組件的更多信息抡医,請參閱:https://github.com/samvermette/SVProgressHUD躲因。
讓我們快速瀏覽一下視圖控制器的代碼早敬,以便對應(yīng)用程序的工作原理有一個好的概念忌傻。MainViewController 是 UIViewController 的一個子類。它的 nib 文件包含一個 UITableView 對象和一個 UISearchBar 對象: