什么是ARC
ARC(Automatic Reference Counting),自動引用計數,是從OX X Lion 和iOS5引入的內存管理技術。在Objective-C中采用ARC機制,讓編譯器來進行內存管理,在ARC有效的狀態下,用戶無需再次鍵入retain或者release代碼,這在降低程序奔潰、內存泄漏等風險的同時,很大程序上減少了開發程序的工作量。編譯器能立刻釋放那些不再被使用的對象。知其然還需知其所以然,本篇文章主要為了簡單介紹iOS開發中的內存管理以及一些提高內存管理的方法。
內存管理/引用計數
Objective-C中的內存管理,也就是引用計數,其機制可以簡單用辦公室的燈光的開關來介紹。假設辦公室里的照明設備只有一個,上班的人進入辦公室,打開燈光,離開辦公室的人,不需要照明了,要把燈關掉。如何管理燈光的開關,保證有人的時候辦公室的燈光是打開的,所有人都離開的時候燈光是關閉的呢,為了判斷是否還有人在辦公室,這里引入計數功能來計算“需要照明的人數”,那么實現該功能的機制如下:
- 第一個人進入辦公室,“需要照明的人數”加1,計數值從0變為1,因此要開燈;
- 之后每當有人進入辦公室,“需要照明的人數”就加1,如,計數值從1變成2;
- 每當有人下班離開辦公室,“需要照明的人數”就減1,如,計數值從2變成1;
- 最后一個人下班離開辦公室是,“需要照明的人數”減1.計數值從1變成0,因此需要關燈。
這就是引用計數的基本原理了,對照照明設備所做的動作和Objective-C 對象的動作如圖一所示。
內存管理的思考方式
其實不管是ARC還是非ARC,內存管理的思考方式都是一樣的:
- 自己生成的對象,自己持有。
- 非自己生成的對象,自己也能持有。
- 自己持有的對象不再需要時釋放。
- 非自己持有的對象無法釋放。
對象操作與Objective-C方法的對應如圖二:
#########非ARC下的理解
自己生成的對象,自己持有
使用以下名稱開頭的方法名意味著自己生成的對象只有自己持有:alloc、new、copy、mutableCopy。
<pre> id obj = [[NSObject alloc] init];//自己生成并持有對象。</pre>非自己生成的對象,自己也能持有
使用alloc、new、copy、mutableCopy之外的方法取得的對象,屬于非自己生成的對象。
<pre>
id obj = [NSMutableArray array]; //取得對象的存在,但自己并不持有對象
[obj retain]; //自己持有對象
</pre>自己持有的對象不再需要時釋放
自己持有的對象不再需要時,持有者有義務釋放該對象,釋放使用release方法。
<pre>
//自己生成并持有的對象使用 release就行了
id obj = [NSObject alloc] init]; //自己生成并持有對象
[obj release]; //釋放對象
//非自己生成并持有的對象,若用retain變為自己持有,也同樣可以用release方法釋放
id obj = [NSMutableArray array]; //取得對象的存在,但自己并不持有對象
[obj retain]; //自己持有對象
[obj release]; //釋放對象
</pre>
- 非自己持有的對象無法釋放
對于用alloc、new、copy、mutableCopy方法生成并持有的對象,或是用retain方法持有的對象,由于持有者是自己,所以在不需要該對象時需要將其釋放。而由此以外所得到的對象,不能再釋放。
<pre>
id obj = [NSObject alloc] init];
[obj release];
[obj release]; //釋放之后再次釋放已非自己持有的對象,應用程序奔潰0
id obj = [NSMutableArray array];
[obj release]; //釋放了非自己生成并持有的對象,應用程序奔潰
</pre>
autorelease
在進入ARC的內存管理之前有必要講一下autorelease。顧名思義,autorelease就是自動釋放,類似于C語言中的自動變量(程序執行時,若某自動變量超出其作用域,該自動變量將被自動廢棄)。
<pre>
{
int a;
} //因為超出變量作用域,自動變量 “int a” 被廢棄,不可再訪問
</pre>
autorelease 會像C語言的自動變量那樣來對待對象實例。當超出其作用域時,對象實例的release實例方法被調用。autorelease的具體使用方法如下:
- 生成并持有NSAutoreleasePool對象;
- 調用已分配對象的autorelease實例方法;
- 廢棄NSAutoreleasePool對象。
<pre>
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];//對于所有調用過autorelease實例方法的對象,在廢棄NSAutoreleasePool 對象時,都將自動調用release方法。
</pre>
強勢插入一個常見iOS面試題目。 不手動指定autoreleasepool的前提下,一個autorealese對象在什么時刻釋放?
上面是經常面試的一道題目,其實主要就是兩點:1,手動干預釋放時機--指定autoreleasepool 就是所謂的:當前作用域大括號結束時釋放。2,系統自動去釋放--不手動指定autoreleasepool(當前的 runloop 迭代結束時釋放)。
再強勢插入一個NSAutoreleasePool的實用范例
在大量產生autorelease對象時,只要不廢棄NSAutoreleasePool對象,那么生成的對象就不能被釋放,因此,有時候會產生內存飆升導致內存不足的現象。典型的例子就是讀入大量圖像的同時改變其尺寸(比如循環壓縮),圖像文件讀入到NSData對象,從中生成UIImage對象,改變對象尺寸后生成新的UIImage對象,這種情況下,就會大量產生autorelease對象。
<pre>
for(int i = 0;i < 圖像數;i++){
//讀入圖像,大量產生autorelease對象,由于沒有廢棄NSAutoreleasePool對象,導致內存激增
}
</pre>
在此情況下,我們可以在適當的地方生成、持有或者廢棄NSAutoreleasePool對象。改進后的方法能顯著減少內存占用(ARC有效的情況下也可以用,用@autoreleasepool()代替手動生成釋放NSAutoreleasePool對象即可)。
<pre>
for(int i = 0;i < 圖像數;i++){
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//讀入大量圖像,大量產生autorelease對象
[pool drain]; //通過釋放NSAutoreleasePool對象,autorelease的對象被一起release
}
</pre>
ARC規則
ARC有效時,對象類型上必須附加所有權修飾符。所有權修飾符一共有4種:
- __strong 修飾符
- __weak 修飾符
- __unsafe_unretained 修飾符
- __autoreleasing 修飾符
__strong 修飾符
__strong修飾符是id類型和對象類型默認的所有權修飾符。id和對象類型在沒有明確指定所有權修飾符時,,默認為__strong。例如,下面兩段代碼是相同的效果:
<pre>
id obj = [[NSObject alloc] init];
id __strong obj = [[NSObject alloc] init];
</pre>
__strong修飾符表示對對象的強引用。持有強引用的變量在超出其作用域時被廢棄,隨著強引用的失效,引用的對象會隨之釋放。
<pre>
{
//自己生成并持有對象
id __strong obj = [[NSObject alloc] init];
} // 因為變量obj超出其作用域,強引用失效,所以自動釋放自己持有的對象,對象的所有者不存在,因此廢棄該對象
</pre>
當然,不僅僅適用于自己生成并持有的對象,非自己生成并持有的對象也適用,附有__strong修飾符的變量可以相互賦值,在賦值上也能夠正確的管理其對象的所有者。
通過__strong修飾符,不必要再次鍵入retain和release,完美的滿足了“內存管理的思考方式”。
__weak 修飾符
看起來通過__strong修飾符就能完美地進行內存管理,但是實際上,僅用__strong就可能會出現引用計數式內存管理中存在的“循環引用”問題。__weak 修飾符就是為了解決循環引用的問題,__weak 修飾符與__strong相反,提供弱引用。弱引用不能持有對象實例。
<pre>
{
//自己生成并持有對象
id __strong obj0 = [[NSObject alloc] init]; //obj0為強引用,所以自己持有對象
id __weak obj1 = obj0; //obj1變量持有生成對象的弱引用
} // 因為變量obj0超出其作用域,強引用失效,所以自動釋放自己持有的對象,對象的所有者不存在,因此廢棄該對象
</pre>
__weak修飾符還有一個優點,在持有某對象的弱引用時,若該對象被廢棄,則此弱引用將自動失效且處于nil被賦值的狀態。
__unsafe_unretained 修飾符
__unsafe_unretained 修飾符,正如其名,是不安全的所有權修飾符。盡管ARC式的內存管理是編譯器的工作,但附有__unsafe_unretained 修飾符的變量不屬于編譯器的內存管理對象。
<pre>
id __unsafe_unretained obj1 = nil;
{
id __strong obj0 = [[NSObject alloc] init]; //obj0為強引用,所以自己持有對象
obj1 = obj0; //雖然obj0變量賦值給obj1,但是obj1變量既不持有對象的強引用也不持有弱引用
NSLog(@"A: %@",obj1); //輸出obj1變量表示的對象
}//因為obj0變量超出其作用域,強引用失效,所以自動釋放自己持有的對象,因為對象無持有者,所以廢棄該對象
NSLog(@"B: %@",obj1); //輸出obj1變量表示的對象 obj1變量表示的對象已經被廢棄(懸垂指針),錯誤訪問
</pre>
在使用__unsafe_unretained修飾符時,賦值給附有__strong修飾符的變量必須確保被賦值的對象卻是存在。在iOS4 以后,蘋果開始用__weak替代了__unsafe_unretained。
__autoreleasing 修飾符
雖然ARC不能直接使用autorelease,但實際上,ARC有效時autorelease功能是其起作用的。
ARC無效的時候用法如下:
<pre>
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];
</pre>
ARC有效的時候用法就改成下面這樣了:
<pre>
@autoreleasepool {
id __autoreleasing obj = [[NSObject alloc] init];
}
</pre>
指定@autoreleasepool快來代替NSAutoreleasePool類對象生成、持有以及廢棄。
NSRunLoop等實現不論ARC有效還是無效,均能夠隨時釋放注冊到autorelease中的對象。
ARC有效下的一些規則:
- 不能使用retain、release、retain、ratainCount、autorelease
- 不能使用NSAllocateObject/NSDeallocateObject
- 必須遵守內存管理的方法命名規則
- 不要顯示調用dealloc
- 使用@autoreleasepool代替NSAutoreleasePool
- 不能使用區域(NSZone)
- 對象型變量不能作為C語言結構體的成員
- 顯示轉換id和void*
id型或對象型變量賦值給void* 或者逆向賦值都需要進行特定的轉換,如果只想單純的賦值,則可以使用”__bridge 轉換“。
屬性
ARC有效的時候的屬性和所有權修飾符對應的關系如圖三所示。