ARC介紹

 ARC也是我想講的一部分內容,很多iOS的程序員到現在也沒有完全轉向ARC,當然各有各的理由,這裏只是轉載了一篇介紹性的文字,簡單介紹了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一次。這對實例變量,sunthesize的變量或者局部變量都是適用的。比如

 

  1. NSString *firstName = self.textField.text; 

firstName現在指向NSString對象,這時這個對象(textField的內容字符串)將被hold住。比如用字符串@“OneV”作爲例子,這個時候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類型的指針也可以指向對象,但是並不會持有該對象。比如:

  1. __weak NSString *weakName = self.textField.text 

得到的指向關係是:

這裏聲明瞭一個weak的指針weakName,它並不持有@“onevcat”。如果self.textField.text的內容發生改變的話,根據之前提到的“只要某個對象被任一strong指針指向,那麼它將不會被銷燬。如果對象沒有被任何strong指針指向,那麼就將被銷燬”原則,此時指向@“onevcat”的指針中沒有strong類型的指針,@”onevcat”將被銷燬。同時,在ARC機制作用下,所有指向這個對象的weak指針將被置爲nil。這個特性相當有用,相信無數的開發者都曾經被指針指向已釋放對象所造成的EXC_BAD_ACCESS困擾過,使用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),因此還是有所不同的。細節的東西我們稍後再說。

注意類似下面的代碼似乎是沒有什麼意義的:

  1. __weak NSString *str = [[NSString alloc] initWithFormat:…];  
  2.         
  3. NSLog(@"%@",str); //輸出是"(null)" 

由於str是weak,它不會持有alloc出來的NSString對象,因此這個對象由於沒有有效的strong指針指向,所以在生成的同時就被銷燬了。如果我們在Xcode中寫了上面的代碼,我們應該會得到一個警告,因爲無論何時這種情況似乎都是不太可能出現的。你可以把weak換成strong來消除警告,或者直接前面什麼都不寫,因爲ARC中默認的指針類型就是strong。

property也可以用strong或weak來標記,簡單地把原來寫retain和assign的地方替換成strong或者weak就可以了。

  1. @property (nonatomic, strong) NSString *firstName;  
  2.          
  3. @property (nonatomic, weak) id  delegate; 

ARC可以爲開發者節省很多代碼,使用ARC以後再也不需要關心什麼時候retain,什麼時候release,但是這並不意味你可以不思考內存管理,你可能需要經常性地問自己這個問題:誰持有這個對象?

比如下面的代碼,假設array是一個NSMutableArray並且裏面至少有一個對象:

  1. id obj = [array objectAtIndex:0];  
  2.        
  3. [array removeObjectAtIndex:0];  
  4.  
  5. NSLog(@"%@",obj); 

在MRC時代這幾行代碼應該就掛掉了,因爲array中0號對象被remove以後就被立即銷燬了,因此obj指向了一個dealloced的對象,因此在NSLog的時候將出現EXC_BAD_ACCESS。而在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是一件不需要考慮的事情。

http://www.onevcat.com/2012/06/arc-hand-by-hand/

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章