談談ARC下的內存管理

什么是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 對象的動作如圖一所示。

圖一.png
內存管理的思考方式

其實不管是ARC還是非ARC,內存管理的思考方式都是一樣的:

  • 自己生成的對象,自己持有。
  • 非自己生成的對象,自己也能持有。
  • 自己持有的對象不再需要時釋放。
  • 非自己持有的對象無法釋放。

對象操作與Objective-C方法的對應如圖二:

圖二.png

#########非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有效的時候的屬性和所有權修飾符對應的關系如圖三所示。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,431評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,637評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,555評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,900評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,629評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,976評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,976評論 3 448
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,139評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,686評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,411評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,641評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,129評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,820評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,233評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,567評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,362評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,604評論 2 380

推薦閱讀更多精彩內容

  • 自動引用計數 自動引用計數:指內存管理中對引用采取自動計數的技術。 內存管理/引用計數 持有對象引起引用計數加...
    南京小伙閱讀 1,329評論 2 3
  • 貌似每個iOS開發者都有一篇屬于自己的內存管理,記錄了自己對內存管理理解的深度以及廣度,所以我也來記錄一下我的理解...
    Bugfix閱讀 2,275評論 0 3
  • 前言 從我開始學習iOS的時候,身邊的朋友、網上的博客都告訴我iOS的內存管理是依靠引用計數的,然后說引用計數大于...
    蓋世英雄_ix4n04閱讀 570評論 0 1
  • 一.內存管理 /引用計數 Objective-C 中的內存管理,也就是引用計數 1.1內存管理的思考方式 自己生成...
    sellse閱讀 317評論 0 0
  • 6、ARC仍然遵循MRC的內存管理方式,4個基本規則在ARC下仍然是有效的,區別只在于:MRC模式下需要手動鍵入r...
    楊淳引閱讀 627評論 0 0