iOS開發 -- ARC與引用計數

以下是關于內存管理的學習筆記:引用計數ARC
iOS5以前自動引用計數(ARC)是在MacOS X 10.7與iOS 5中引入一項新技術,用于代替之前的手工引用計數MRC(Manual Reference Counting)管理Objective-C中的對象【官方也叫MRR(Manual Retain Release)】。如今,ARC下的iOS項目幾乎把所有內存管理事宜都交給編譯器來決定,而開發者只需專注于業務邏輯。
但是,對于iOS開發來說,內存管理是個很重要的概念,如果先要寫出內存使用效率高而又沒有bug的代碼,就得掌握其內存管理模型的細節。

一、引用計數

1.與內存管理的關系?

在Objective-C內存管理中,每個對象都有屬于自己的計數器:如果想讓某個對象繼續存活(例如想對該對象進行引用),就遞增它的引用計數;當用完它之后,就遞減該計數;當沒人引用該對象,它的計數變為0之后,系統就把它銷毀。
這個,就是引用計數在其中充當的角色:用于表示當前有多少個對象想令此對象繼續存活程序中;

2.引用計數的介紹:

引用計數(Reference Count),也叫保留計數(retain count),表示對象被引用的次數。一個簡單而有效的管理對象生命周期的方式。

3.引用計數的工作原理:

當我們創建(alloc)一個新對象A的時候,它的引用計數從零變為 1;
當有一個指針指向這個對象A,也就是某對象想通過引用保留(retain)該對象A時,引用計數加 1;
當某個指針/對象不再指向這個對象A,也就是釋放(release)該引用后,我們將其引用計數減 1;
當對象A的引用計數變為 0 時,說明這個對象不再被任何指針指向(引用)了,這個時候我們就可以將對象A銷毀,所占內存將被回收,且所有指向該對象的引用也都變得無效了。系統也會將其占用的內存標記為“可重用”(reuse);

流程參考圖如下:


(圖片表格取自《編寫高質量iOS與OS X代碼的52個有效方法》一書)

4.操作引用計數的那些方法:

A.以下是NSObject協議中聲明的3個用于操作計數器的方法:

retain : 保留。保留計數+1;
release : 釋放。保留計數 -1;
autorelease :稍后(清理“自動釋放池”時),再遞減保留計數,所以作用是延遲對象的release;

B.dealloc方法:

另外,當計數為0的時候對象會自動調用dealloc。而我們可以在dealloc方法做的,就是釋放指向其他對象的引用,以及取消已經訂閱的KVO、通知;(自己不能調用dealloc方法,因為運行期系統會在恰當的時候調用它,而且一旦調用dealloc方法,對象不再有效,即使后續方法再次調用retain。)
所以,調用release后會有2種情況:
調用前計數>1,計數減1;
調用前計數<1,對象內存被回收;

C.retainCount:獲取引用計數的方法。

Eg: [object retainCount]; //得到object的引用計數

retain、autorelease、release方法詳解

retain作用:

調用后計數+1,保留對象操作。但是當對象被銷毀、內存被回收的時候,即使使用retain也不再有效;

autorelease作用:

autorelease不立即釋放,而是注冊到autoreleasepool(自動釋放池)中,等到pool結束時釋放池再自動調用release進行釋放工作。
autorelease看上去很像ARC,但是實際上更類似C語言中的自動變量(局部變量),當某自動變量超出其作用域(例如大括號),該自動變量將被自動廢棄,而autorelease中對象實例的release方法會被調用;[與C不同的是,開發者可以設定變量的作用域。]
釋放時間:每個Runloop中都創建一個Autorelease pool(自動釋放池),每一次的Autorelease,系統都會把該Object放入了當前的Autorelease pool中,并在Runloop的末尾進行釋放,而當該pool被釋放時,該pool中的所有Object會被調用Release。 所以,一般情況下,每個接受autorelease消息的對象,都會在下個Runloop開始前被釋放。
例如可用以下場景:(需要從ARC改為使用手動管理的可以做如下的設置: 在Targets的Build Phases選項下Compile Sources下選擇要不使用ARC編譯的文件,雙擊它,輸入-fno-objc-arc即可使用MRC手工管理內存方式;)

-(NSString *)getSting{ 
  NSString *str = [[NSString alloc]initWithFormat:@"I am Str"];
  return [str autorelease];
}

自動釋放池中的釋放操作會等到下一次時間循環時才會執行,所以調用以下:

NSString *str = [self getSting];NSLog(@"%@",str);

返回的str對象得以保留,延遲釋放。因此可以無需再NSLog語句之前執行保留操作,就可以將返回的str對象輸出。
所以可見autorelease的作用是能延長對象的生命期。使其在跨越方法調用邊界后依然可以存活一段時間。

release作用:

release會立即執行釋放操作,使得計減1;
有這樣一種情況:當某對象object的引用計數為1的時候,調用“[object release];”,此時如果再調用NSLog方法輸出object的話,可能程序就會崩潰,當然只是有可能,因為對象所占內存在“解除分配(deallocated)”之后,只是放回“可用內存池(avaiable pool)”,但是如果執行NSLog時,尚未覆寫對象內存,那么該對象依然有效,所以程序有可能不會崩潰,由此可見,因過早地釋放對象而導致的bug很難調試。
為避免這種情況,一般調用完對象之后都會清空指針:"object = nil",這樣就能保證不會出現指向無效對象的指針,也就是懸掛指針(dangling pointer);
懸掛指針:指向無效對象的指針。

那么,向已經釋放(dealloc)的對象發送消息,retainCount會是多少?

原則是不可以這么做。因為該對象的內存已經被回收,而我們向一個已經被回收的對象發了一個 retainCount 消息,所以它的輸出結果應該是不確定的,例如為減少一次內存的寫操作,不將這個值從 1 變成 0,所以很大可能輸出1。例如下面這種情況:

Person *person = [[Person alloc] init]; //此時,計數 = 1   
[person retain];  //計數 = 2  
[person release];  //計數 = 1   
[person release]; //很可能計數 = 1;  

雖然第四行代碼把計數1release了一次,原理上person對象的計數會變成0,但是實際上為了優化對象的釋放行為,提高系統的工作效率,在retainCount為1時release系統會直接把對象回收,而不再為它的計數遞減為0,所以一個對象的retainCount值有可能永遠不為0;
因此,不管是否為ARC的開發環境中,也不推薦使用retainCount來做為一個對象是否存在于內存之中的依據。
<br /><br />


二、ARC

1.背景:

ARC是iOS 5推出的新功能,全稱叫 ARC(Automatic Reference Counting)。
即使2014 年的 WWDC 大會上推出的Swift 語言,該語言仍然使用 ARC 技術作為其管理方式。

2.ARC是什么?

需要注意的是,ARC并不是GC(Garbage Collection 垃圾回收器),它只是一種代碼靜態分析(Static Analyzer)工具,背后的原理是依賴編譯器的靜態分析能力,通過在編譯時找出合理的插入引用計數管理代碼,從而提高iOS開發人員的開發效率。
Apple的文檔里是這么定義ARC的:
“自動引用計數(ARC)是一個編譯器級的功能,它能簡化Cocoa應用中對象生命周期管理(內存管理)的流程。”

3.ARC在做什么?

在編譯階段,編譯器將在項目代碼中自動為分配對象插入retain、release和autorelease,且插入的代碼不可見。
但是,需要注意的是,ARC模式下引用計數規則還起作用,只是編譯器會為開發者分擔大部分的內存管理工作,除了插入上述代碼,還有一部分優化以及分析內存的管理工作。
作用:
a.降低內存泄露等風險 ;
b.減少代碼工作量,使開發者只需專注于業務邏輯;

4.ARC具體為引用計數做了哪些工作?

編譯階段自動添加代碼:

編譯器會在編譯階段以恰當的時間與地方給我們填上原本需要手寫的retain、release、autorelease等內存管理代碼,所以ARC并非運行時的特性,也不是如java中的GC運行時的垃圾回收系統;因此,我們也可以知道,ARC其實是處于編譯器的特性。
例如:

-(void)setup{ 
   _person = [person new];
}

在手工管理內存的環境下,_person是不會自動保留其值,而在ARC下編譯,其代碼會變成:

-(void)setup{ 
  person *tmp = [person new];
  _person = [tmp retain]; 
  [tmp release];
}

當然,在開發工作中,retain和release對于開發人員來說都可以省去,由ARC系統自動補全,達到同樣的效果。
但實際上,ARC系統在自動調用這些方法時,并不通過普通的Objective-C消息派發控制,而是直接調用底層C語言的方法:
比如retain,ARC在分析到某處需要調用保留操作的地方,調用了與retain等價的底層函數 objc_retain,所以這也是ARC下不能覆寫retain、release或者autorelease的原因,因為這些方法在ARC從來不會被直接調用。

運行期組件的優化:

ARC是編譯器的特性,但也包含了運行期組件,所執行的優化很有意義。
例子:
person工廠方法personWithName可以得到一個person對象,在這里調用并賦值給person的一個實例_one:

_one = [person personWithName:@"name"];

可能會出現這種情況:
在personWithName方法中,返回對象給_one之前,為其調用了一次autorelease方法。
由于實例變量是個強引用,所以編譯器會在設置其值的時候還需要執行一次保留操作。

//在personWithName方法返回前已有調用一次autorelease方法進行保留操作;
person *tmp = [person personWithName:@"name"]; 

_one = [tmp retain];

很明顯,autorelease與緊跟其后的retain是重復的。為提升性能,可以將二者刪去,舍棄autorelease這個概念,并且規定返回對象的技術都比期望值多1,但是為了向后兼容非ARC等情況.
ARC采取了另外一種方式:
ARC可以在運行期檢測到這一對多余的操作。所以在返回對象時,不直接調用autorelease,改為調用objc_autoreleaseReturnValue,用來檢測返回之后即將要執行的代碼中,含有retain操作,則設置全局數據結構(此數據結構具體內容因處理器而異)中的一個標志位,而不執行autorelease操作。
同樣,若方法返回一個自動釋放對象,調用personWithName方法的代碼段不執行retain,改為執行objc_retainAutoreleaseReturnValue函數。此函數檢測剛才的那個標志位,若已經置位了,則不執行retain操作。

而設置并檢測標志位,要比調用autorelease和retain更快,這就使得這一情況的處理得到優化。
修改2個函數后優化完整結果如下: 【例子來自《編寫高質量iOS與OS X代碼的52個有效方法》一書P126】


我們可以通過兩個函數的偽代碼大致描述如下:

像是
objc_autoreleaseReturnValue
這個函數是如何檢測方法調用者是否會立刻保留對象呢,這就要交給處理器來解決了。
由于必須查看原始機器碼指令方可判斷出這一點需要處理器來定。
所以,其實只有編譯器的作者才能知道這里是如何實現此函數的。
ARC的安全性:
在編寫屬性的設置方法(setter)時,如果使用手工管理方式,可能會需要如下編寫:

-(void)setObject:(id)object{ 
   [_object release]; 
   _object = [object retain];
}

但是這樣寫會出現問題:如果說新值object和實例變量_object的值是相同的,而且只有當前實例變量對象還在引用這個值,那么設置方法中的釋放操作會使得該值保留計數為0,系統將其回收,所以接下來的保留操作,將會令應用程序崩潰。而在使用ARC的環境下,就不可能會發送這樣的的“邊界情況”了:

剛才的代碼在ARC下可以這樣寫:(當然,我們知道如果不需要覆寫setter方法,也可以不編寫此方法,直接使用"self.object = xxx"也可以安全地調用。):

-(void)setObject:(id)object{ 
   _object = object;
}

而且ARC會用一種安全的方式來設置:先保留新值,再釋放舊值,最后設置實例變量。
在手工管理的情況下,我們需要特別注意這種"邊緣情況",但是ARC下,我們就可以很輕松地編寫這種代碼了,而不用去考慮這種情況如何處理了。
總結:將內存管理交由編譯器運行期組件來做,可以使代碼得到多種優化,而上面是其中一種方式。

5.ARC下需要注意的規則

a.關于dealloc:
. 不能顯式調用dealloc;
. 不能再dealloc中調用【super dealloc】(非ARC下則需要調用.);
. 不能在dealloc 中釋放資源(非ARC下需要釋放不同的對象);
b.以及,不能顯式調用以下代碼:


(NSZone:內存區)

c.不能再使用NSAutoreleasePool對象,ARC提供了@autoreleasepool塊來代替它,這樣更有效率;

6.所有權修飾符

oc編程中為了處理對象,可將變量類型定義為id類型或各種對象類型。使用這些限定符可以確切地聲明對象變量和屬性的生命周期;
所謂對象類型就是指向NSObject這樣的oc類的指針,例如“NSObject *”。id類型用于隱藏對象類型的類名部分。相當于C語言中常用的“void *”;
ARC下,id類型和對象類型上必須附加所有權修飾符;
所有權修飾符一共有4種:
__strong:
強引用,可以引用別的對象為強引用,相當于retain的特性;表明變量持有alloc/new/copy/mutableCopy方法群創建的對象的強引用,強引用變量會在其作用域里被保留,在超出作用域后被釋放,為默認的修飾符;
例如以下代碼
id objc = [[NSObject alloc] init];
實際上已被附上所有權修飾符:
id __strong objc = [[NSObject alloc] init];

__weak:
使用__strong,有可能2個對象相互強引用或者1個對象對自身強引用則會發生循環引用(如下圖,或者叫保留環),所以當對象在超出其生存周期后,本應被系統廢棄卻仍然被引用者所持有,所以造成內存泄露(應當廢棄的對象在超出生命周期后,繼續存在);

(2個對象互相強引用)
(某對象對自身強引用)

而當我們對可能會發送循環引用的對象進行__weak弱引用修飾,弱引用變量不會持有對象,且生成的對象會立刻釋放,可避免循環引用,并且弱引用還有另外一個特點,若對象被系統回收,該弱引用變量將自動失效并且賦值為nil。
__unsafe_unretained: 不安全的所有權修飾符,ARC的內存管理是編譯器的工作,而附有__unsafe_unretained修飾符的變量不屬于編譯器的內存管理對象。與__weak作用一樣,也可以避免循環引用;但是不同的是,__unsafe_unretained屬性的變量不會將變量設置為nil,而是就處于于懸掛狀態;

__autoreleasing:在ARC中使用“@autoreleasepool塊”來取代“NSAutoreleasePool”類對象的生成,通過將對象賦值給附加了__autoreleasing修飾符的變量來替代調用autorelease方法;

Other:ARC需要注意的事項?

1.過度使用 block 之后,無法解決循環引用問題。
2.遇到底層 Core Foundation 對象,需要自己手工管理它們的引用計數時,我們需轉換關鍵字,作為橋接轉換以解決 Core Foundation 對象與 Objective-C 對象相對轉換的問題:
__bridge:使用__bridge標記可以在不修改相關對象的引用計數的情況下,將對象從Core Foundation框架數據類型轉換為Foundation框架數據類型(反之亦然)。
__bridge_retained:會將相關對象的引用計數加 1,并且可以將Core Foundation框架數據類型對象轉換為Foundation框架數據類型對象,并從ARC接管對象的所有權。
__bridge_transfer:可以將Foundation框架數據類型對象轉換為Core Foundation框架數據類型對象,并且會將對象的所有權交給ARC管理,也就是說引用計數交由ARC管理;

總結:就推薦2本經典的書(估計很多人早就看完了?? ),書本也好,pdf也好,建議看一下:
《Effective Objective-C 2.0 編寫高質量iOS與OS X代碼的52個有效方法》
《Objective-C高級編程 iOS與OS X多線程和內存管理》
<br />


(轉載請標明原文出處,謝謝支持 ~ - ~)
? by:啊左~

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

推薦閱讀更多精彩內容