歷史版本程序員
ARC(Automatic Reference Counting,自動引用計數)極大地減小了Cocoa開發中的常見編程錯誤:retain
跟release
不匹配。ARC並不會消除對retain
和release
的調用,而是把這項本來大都屬於開發者的工做移交給了編譯器。這樣作的好處是顯而易見的,可是必須知道retain
和release
是仍然在使用的。ARC並不等同垃圾回收。思考下面這段代碼,它對一個實例變量賦值:編程
1 2 3 |
@property (nonatomic, readwrite, strong) NSString *title;
...
_title = [NSString stringWithFormat:@"Title"];
|
在非ARC環境中,_title
沒有被保留過,那麼賦給它的NSString
就是autorelease
的,會在事件循環結束時被釋放。以後若是再次訪問_title
,程序就會崩潰。這類錯誤極其常見,調試起來會異常困難。另外,若是_title
以前已經有一個值,那舊的值就會由於沒有被釋放掉而引發內存泄漏。markdown
使用ARC,編譯器會在合適的位置自動插入一些代碼,下面的代碼與前面的代碼是等同的:工具
1 2 3 4 |
id oldTitle = _title;
_title = [NSString stringWithFormat:@"Title"];
[_title retain];
[oldTitle release];
|
retain
和release
仍然會被調用,因此有一些開銷,在release
的時候可能還會調用dealloc
方法。這段代碼與程序員手動調用retain
和release
的代碼在運行結果上是徹底一致的。垃圾回收機制是在運行時起做用的,會影響運行效率,而ARC是在編譯時插入內存管理代碼,不影響運行時效率,所以內存回收比垃圾回收時的效率要高,可以提高系統性能。正如其餘一些編譯器優化方式同樣,這種編譯器能夠自由地以多種方式優化內存管理,而讓程序員手動去作這些工做是不現實的。在多數狀況下,使用ARC生成內存管理代碼的程序比程序員手工添加內存管理代碼的對等程序運行更快!性能
ARC不是垃圾回收,尤爲是它不能像Snow Leopard中的垃圾回收機制那樣處理循環引用(保留)。圖3-1中的對象A與對象B之間存在循環保留。優化
圖3-1 循環保留atom
在Snow Leopard的垃圾回收機制下,若是從「外部對象」到「對象A」的引用連接中斷了,對象A和對象B都會被銷燬,由於它們從程序中孤立出去了。而在ARC中,對象A和對象B都不會被銷燬,由於它們的引用計數都大於0。所以,在iOS開發中,必需要作好對強引用(strong reference)的跟蹤管理以避免出現循環引用。spa
屬性關係有兩種主要類型:strong
和weak
,至關於非ARC環境裏的retain
和assign
。只要存在一個強引用,對象就會一直存在,不會被銷燬。強引用相似於C++中的shared_ptr
,只不過管理引用計數的代碼是在編譯時生成的,而shared_ptr
是在運行時經過操做符重載肯定的。指針
Objective-C中一直存在循環引用的問題,但在實際應用中不多出現循環引用。對於過去那些使用assign
屬性的地方,在ARC環境中要使用weak
代替。大部分引用循環是由委託(delegate)引發的,因此應該老是把delegate
屬性聲明爲weak
。當引用的對象被銷燬以後,weak
引用會被自動置爲nil
,與assign
相比這是一個巨大的進步,由於assign
能夠指向被釋放掉的內存,致使程序崩潰。調試
在ARC出現之前,合成屬性(synthesized property)的默認存儲類型是assign
;而在ARC中,默認的存儲類型是strong
。這在進行代碼轉換時可能會形成一些困惑。建議爲全部屬性顯式指明存儲類型。 循環保留產生的另外一個主要緣由是塊,其介紹詳見第23章。
將代碼轉換到ARC的時候,要注意如下兩點。
retain
、release
、autorelease
,能夠直接將這些代碼刪除。ARC在編譯時會自動在合適的位置插入內存管理代碼。dealloc
方法只是用於釋放實例變量,那麼dealloc
方法也沒有存在的必要了,能夠直接刪掉。在任何狀況下都不須要再使用release
,ARC會自動處理好這些事情。若是仍然須要使用dealloc
來作一些其餘的事情(好比移除KVO觀察),記得不要再調用[super dealloc]
,不然編譯器會給出錯誤信息。正如前面所說的,ARC並非垃圾回收,它是編譯器的一種功能,能夠在代碼中合適的位置自動插入調用retain
和release
的語句。這也意味着,若是現存的使用手動內存管理方式的代碼很好地遵照了命名約定,那麼這些代碼跟ARC就是徹底互通的。舉例來講,若是調用了一個名爲copySomething
的方法,ARC會認爲這個方法會對返回對象的引用計數加1,若是有必要,ARC會在合適的位置插入一個release
。引用計數的加1操做既能夠在ARC代碼中進行,也能夠在某段手動進行內存管理的代碼中進行,這對於ARC來講是無所謂的。
可是,若是沒有遵照Cocoa命名約定,這種狀況下就會出錯。好比有一個名爲copyRight
的方法,它以autorelease
的形式返回一個版權信息的字符串,這種狀況下ARC的行爲取決於調用代碼和被調用代碼是否都是使用ARC進行編譯的。
方法名是以copy
開頭的,所以ARC認爲copyRight
方法會對返回對象的引用計數加1。若是copyRight
方法的代碼與調用copyRight
方法的代碼都是使用ARC編譯的,ARC就會在copyRight
方法代碼中添加一個retain
語句,同時在調用copyRight
方法的代碼中添加一個release
語句,這種狀況下程序依然會正常工做。雖然對執行效率有一點點影響,可是程序不會崩潰,也不會形成內存泄漏。
然而,若是調用copyRight
方法的代碼是使用ARC編譯的(ARC就會在代碼後邊添加一個release
語句),而copyRight
方法代碼沒有使用ARC編譯的話(方法體中就沒有retain
語句),程序就會崩潰。反過來,若是copyRight
方法代碼使用ARC編譯(ARC就會在方法代碼中添加一個retain
語句),而調用代碼沒有使用ARC編譯的話(調用代碼中就不會有release
語句),代碼就會形成內存泄漏。
最好的解決辦法就是遵照Cocoa的命名約定。在這個例子中,若是將方法名改成copyright
,就能夠徹底避免這種問題。ARC根據駝峯式大小寫方式對方法的名稱進行單詞分隔,而後使用相應的內存管理規則。
若是對一個錯誤命名的方法進行重命名是不現實的,那麼還能夠在方法聲明中使用NS_RETURNS_RETAINED
或者NS_RETURNS_NOT_RETAINED
修飾符來告訴編譯器應該使用哪一種內存管理規則。這些修飾符是在NSObjCRuntime.h中定義的。
爲了讓編譯器可以恰當地添加retain
和release
語句,編寫ARC代碼時須要注意如下四點。
不要調用retain
、release
和autorelease
。直接將這些代碼刪除,這是最簡單的規則。另外,也不能夠覆蓋這幾個方法(永遠不要試圖去覆蓋這幾個方法)。若是你想實現Singleton模式,請閱讀第4章,瞭解如何在不覆蓋這些方法的狀況下實現Singleton模式。
C語言結構體中不要有對象指針。這種狀況不多出現。可是若是C語言結構體中確實有一個對象,要麼把它存儲到另外一個對象中,要麼就把它轉換成void*
類型(下一條規則會介紹如何轉換到void*
)。能夠在任什麼時候候經過調用free
方法銷燬一個C語言結構體,這會影響到對結構體中對象的自動跟蹤。
id
和void*
類型只能經過橋接轉換進行轉換。在使用了Core Foundation的代碼中會有不少這種狀況。第27章會詳細介紹橋接轉換及如何結合ARC來使用。
不要使用NSAutoreleasePool
。不要手動建立本身的自動釋放池,直接把須要本身的池的代碼放入@autoreleasepool{}
代碼塊中便可。若是你有一些特殊的代碼用於控制自動釋放池的釋放,那些代碼實際上極可能是沒必要要的。@autoreleasepool
比NSAutoreleasePool
快了大約20倍。
對於大部分代碼來講,只要遵照這些規則就不會有任何問題。在Xcode中有一個小工具能夠替你完成大部分代碼轉換的工做,它位於Edit → Refactor → Convert to Objective-C ARC。
ARC應該是Objective-C中繼自動釋放池以後最重要的改進了,應該儘量地使用。若是不能將代碼所有轉換到ARC,就儘量地轉換。使用ARC編譯的程序速度更快,bug更少,也比手動內存管理代碼更容易編寫。如今就開始使用ARC吧!