ARC入門
手把手教你ARC——iOS/Mac開發ARC入門和使用
本文部分實例取自iOS 5 Toturail一書中關於ARC的教程和公開內容,僅用於技術交流和討論。請不要將本文的部分或全部內容用於商用,謝謝合作。
歡迎轉載本文,但是轉載請注明本文出處:https://www.onevcat.com/2012/06/arc-hand-by-hand/
本文適合人群:對iOS開發有一定基礎,熟悉iOS開發中內存管理的Reference Counting機製,對ARC機製有聽聞很向往但是一直由於種種原因沒有使用的童鞋。本文將從ARC機理入手對這個解放廣大iOS開發者的偉大機製進行一個剖析,並逐步引導你開始使用ARC。一旦習慣ARC,你一定會被它的簡潔高效所征服。
寫在開頭
雖然距離WWDC2011和iOS 5已經快一年時間,但是很多開發者並沒有利用新方法來提高自己的水平,這點在ARC的使用上非常明顯(特別是國內,基本很少見到同行轉向ARC)。我曾經詢問過一些同行為什麼不轉向使用ARC,很多人的回答是擔心內存管理不受自己控製..其實我個人認為這是對於ARC機製了解不足從而不自信,所導致的對新事物的恐懼。而作為最需要“追趕時髦”的職業,這樣的心態將相當不利。謹以此文希望能清楚表述ARC的機理和用法,也希望能夠成為現在中文入門教學缺失的補充。
什麼是ARC
Automatic Reference Counting,自動引用計數,即ARC,可以說是WWDC2011和iOS5所引入的最大的變革和最激動人心的變化。ARC是新的LLVM 3.0編譯器的一項特性,使用ARC,可以說一舉解決了廣大iOS開發者所憎恨的手動內存管理的麻煩。
在工程中使用ARC非常簡單:隻需要像往常那樣編寫代碼,隻不過永遠不寫retain
,release
和autorelease
三個關鍵字就好~這是ARC的基本原則。當ARC開啟時,編譯器將自動在代碼合適的地方插入retain
, release
和autorelease
,而作為開發者,完全不需要擔心編譯器會做錯(除非開發者自己錯用ARC了)。好了,ARC相當簡單吧~到此為止,本教程結束。
等等…也許還有其他問題,最嚴重的問題是“我怎麼確定讓ARC來管理不會出問題?”或者“用ARC會讓程序性能下降吧”。對於ARC不能正處理內存管理的質疑自從ARC出生以來就一直存在,而現在越來越多的代碼轉向ARC並取得了很好的效果,這證明了ARC是一套有效的簡化開發複雜程度的機製,另外通過研究ARC的原理,可以知道使用ARC甚至能提高程序的效率。在接下來將詳細解釋ARC的運行機理並且提供了一個step-by-step的教程,將非ARC的程序轉換為ARC。
ARC工作原理
手動內存管理的機理大家應該已經非常清楚了,簡單來說,隻要遵循以下三點就可以在手動內存管理中避免絕大部分的麻煩:
如果需要持有一個對象,那麼對其發送retain 如果之後不再使用該對象,那麼需要對其發送release(或者autorealse) 每一次對retain,alloc或者new的調用,需要對應一次release或autorealse調用
初學者可能僅僅隻是知道這些規則,但是在實際使用時難免犯錯。但是當開發者經常使用手動引用計數 Manual Referecen Counting(MRC)的話,這些規則將逐漸變為本能。你會發現少一個release
的代碼怎麼看怎麼別扭,從而減少或者杜絕內存管理的錯誤。可以說MRC的規則非常簡單,但是同時也非常容易出錯。往往很小的錯誤就將引起crash或者OOM之類的嚴重問題。
在MRC的年代裏,為了避免不小心忘寫release
,Xcode提供了一個很實用的小工具來幫助可能存在的代碼問題(Xcode3裏默認快捷鍵Shift+A?不記得了),可以指出潛在的內存泄露或者過多釋放。而ARC在此基礎上更進一步:ARC是Objective-C編譯器的特性,而不是運行時特性或者垃圾回收機製,ARC所做的隻不過是在代碼編譯時為你自動在合適的位置插入release
或autorelease
,就如同之前MRC時你所做的那樣。因此,至少在效率上ARC機製是不會比MRC弱的,而因為可以在最合適的地方完成引用計數的維護,以及部分優化,使用ARC甚至能比MRC取得更高的運行效率。
ARC機製
學習ARC很簡單,在MRC時代你需要自己retain
一個想要保持的對象,而現在不需要了。現在唯一要做的是用一個指針指向這個對象,隻要指針沒有被置空,對象就會一直保持在堆上。當將指針指向新值時,原來的對象會被release
一次。這對實例變量,synthesize的變量或者局部變量都是適用的。比如
NSString *firstName = self.textField.text;
firstName
現在指向NSString對象,這時這個對象(textField
的內容字符串)將被hold住。比如用字符串@“OneV"作為例子(雖然實際上不應該用字符串舉例子,因為字符串的retainCount規則其實和普通的對象不一樣,大家就把它當作一個普通的對象來看吧…),這個時候firstName
持有了@"OneV"。
當然,一個對象可以擁有不止一個的持有者(這個類似MRC中的retainCount>1的情況)。在這個例子中顯然self.textField.text
也是@“OneV",那麼現在有兩個指針指向對象@"OneV”(被持有兩次,retainCount=2,其實對NSString對象說retainCount是有問題的,不過anyway~就這個意思而已.)。
過了一會兒,也許用戶在textField
裏輸入了其他的東西,那麼self.textField.text
指針顯然現在指向了別的字符串,比如@“onevcat",但是這時候原來的對象已然是存在的,因為還有一個指針firstName
持有它。現在指針的指向關係是這樣的:
隻有當firstName
也被設定了新的值,或者是超出了作用範圍的空間(比如它是局部變量但是這個方法執行完了或者它是實例變量但是這個實例被銷毀了),那麼此時firstName
也不再持有@“OneV",此時不再有指針指向@"OneV",在ARC下這種狀況發生後對象@"OneV"即被銷毀,內存釋放。
類似於firstName
和self.textField.text
這樣的指針使用關鍵字strong
進行標誌,它意味著隻要該指針指向某個對象,那麼這個對象就不會被銷毀。反過來說,ARC的一個基本規則即是,隻要某個對象被任一strong
指針指向,那麼它將不會被銷毀。如果對象沒有被任何strong指針指向,那麼就將被銷毀。在默認情況下,所有的實例變量和局部變量都是strong
類型的。可以說strong
類型的指針在行為上和MRC時代retain
的property是比較相似的。
既然有strong
,那肯定有weak
咯~weak
類型的指針也可以指向對象,但是並不會持有該對象。比如:
__weak NSString *weakName = self.textField.text
得到的指向關係是:
這裏聲明了一個weak
的指針weakName
,它並不持有@“onevcat"。如果self.textField.text
的內容發生改變的話,根據之前提到的"隻要某個對象被任一strong指針指向,那麼它將不會被銷毀。如果對象沒有被任何strong指針指向,那麼就將被銷毀”原則,此時指向@“onevcat"的指針中沒有strong
類型的指針,@"onevcat"將被銷毀。同時,在ARC機製作用下,所有指向這個對象的weak
指針將被置為nil
。這個特性相當有用,相信無數的開發者都曾經被指針指向已釋放對象所造成的EXCBADACCESS困擾過,使用ARC以後,不論是strong
還是weak
類型的指針,都不再會指向一個dealloced的對象,從根源上解決了意外釋放導致的crash。
不過在大部分情況下,weak
類型的指針可能並不會很常用。比較常見的用法是在兩個對象間存在包含關係時:對象1有一個strong
指針指向對象2,並持有它,而對象2中隻有一個weak
指針指回對象1,從而避免了循環持有。一個常見的例子就是oc中常見的delegate設計模式,viewController中有一個strong
指針指向它所負責管理的UITableView,而UITableView中的dataSource
和delegate
指針都是指向viewController的weak
指針。可以說,weak
指針的行為和MRC時代的assign
有一些相似點,但是考慮到weak
指針更聰明些(會自動指向nil),因此還是有所不同的。細節的東西我們稍後再說。
注意類似下麵的代碼似乎是沒有什麼意義的:
__weak NSString *str = [[NSString alloc] initWithFormat:…];
NSLog(@"%@",str); //輸出是"(null)"
由於str
是weak
,它不會持有alloc出來的NSString
對象,因此這個對象由於沒有有效的strong
指針指向,所以在生成的同時就被銷毀了。如果我們在Xcode中寫了上麵的代碼,我們應該會得到一個警告,因為無論何時這種情況似乎都是不太可能出現的。你可以把weak換成strong來消除警告,或者直接前麵什麼都不寫,因為ARC中默認的指針類型就是strong
。
property也可以用strong
或weak
來標記,簡單地把原來寫retain
和assign
的地方替換成strong
或者weak
就可以了。
@property (nonatomic, strong) NSString *firstName;
@property (nonatomic, weak) id delegate;
ARC可以為開發者節省很多代碼,使用ARC以後再也不需要關心什麼時候retain
,什麼時候release
,但是這並不意味你可以不思考內存管理,你可能需要經常性地問自己這個問題:誰持有這個對象?
比如下麵的代碼,假設array
是一個NSMutableArray
並且裏麵至少有一個對象:
id obj = [array objectAtIndex:0];
[array removeObjectAtIndex:0];
NSLog(@"%@",obj);
在MRC時代這幾行代碼應該就掛掉了,因為array
中0號對象被remove以後就被立即銷毀了,因此obj指向了一個dealloced的對象,因此在NSLog的時候將出現EXCBADACCESS。而在ARC中由於obj是strong
的,因此它持有了array
中的首個對象,array
不再是該對象的唯一持有者。即使我們從array
中將obj移除了,它也依然被別的指針持有,因此不會被銷毀。
一點提醒
ARC也有一些缺點,對於初學者來說,可能僅隻能將ARC用在objective-c對象上(也即繼承自NSObject的對象),但是如果涉及到較為底層的東西,比如Core Foundation中的malloc()或者free()等,ARC就鞭長莫及了,這時候還是需要自己手動進行內存管理。在之後我們會看到一些這方麵的例子。另外為了確保ARC能正確的工作,有些語法規則也會因為ARC而變得稍微嚴格一些。
ARC確實可以在適當的地方為代碼添加retain
或者release
,但是這並不意味著你可以完全忘記內存管理,因為你必須在合適的地方把strong
指針手動設置到nil,否則app很可能會oom。簡單說還是那句話,你必須時刻清醒誰持有了哪些對象,而這些持有者在什麼時候應該變為指向nil
。
ARC必然是Objective-C以及Apple開發的趨勢,今後也會有越來越多的項目采用ARC(甚至不排除MRC在未來某個版本被棄用的可能),Apple也一直鼓勵開發者開始使用ARC,因為它確實可以簡化代碼並增強其穩定性。可以這麼說,使用ARC之後,由於內存問題造成的crash基本就是過去式了(OOM除外 :P)
我們正處於由MRC向ARC轉變的節點上,因此可能有時候我們需要在ARC和MRC的代碼間來回切換和適配。Apple也想到了這一點,因此為開發這提供了一些ARC和非ARC代碼混編的機製,這些也將在之後的例子中列出。另外ARC甚至可以用在C++的代碼中,而通過遵守一些代碼規則,iOS 4裏也可以使用ARC(雖然我個人認為在現在iOS 6都唿之欲出的年代已經基本沒有需要為iOS 4做適配的必要了)、
總之,聰明的開發者總會嚐試盡可能的自動化流程,已減輕自己的工作負擔,而ARC恰恰就為我們提供了這樣的好處:自動幫我們完成了很多以前需要手動完成的工作,因此對我來說,轉向ARC是一件不需要考慮的事情。
最後更新:2017-04-03 05:39:19