以下是關(guān)于內(nèi)存管理的學(xué)習(xí)筆記:引用計數(shù)與ARC者蠕。
iOS5以前自動引用計數(shù)(ARC)是在MacOS X 10.7與iOS 5中引入一項(xiàng)新技術(shù)帅涂,用于代替之前的手工引用計數(shù)MRC(Manual Reference Counting)管理Objective-C中的對象【官方也叫MRR(Manual Retain Release)】摔寨。如今省店,ARC下的iOS項(xiàng)目幾乎把所有內(nèi)存管理事宜都交給編譯器來決定涤浇,而開發(fā)者只需專注于業(yè)務(wù)邏輯。
但是例朱,對于iOS開發(fā)來說孝情,內(nèi)存管理是個很重要的概念,如果先要寫出內(nèi)存使用效率高而又沒有bug的代碼洒嗤,就得掌握其內(nèi)存管理模型的細(xì)節(jié)箫荡。
一、引用計數(shù)
1.與內(nèi)存管理的關(guān)系渔隶?
在Objective-C內(nèi)存管理中羔挡,每個對象都有屬于自己的計數(shù)器:如果想讓某個對象繼續(xù)存活(例如想對該對象進(jìn)行引用),就遞增它的引用計數(shù)派撕;當(dāng)用完它之后婉弹,就遞減該計數(shù);當(dāng)沒人引用該對象终吼,它的計數(shù)變?yōu)?之后镀赌,系統(tǒng)就把它銷毀。
這個际跪,就是引用計數(shù)在其中充當(dāng)?shù)慕巧河糜诒硎井?dāng)前有多少個對象想令此對象繼續(xù)存活程序中商佛;
2.引用計數(shù)的介紹:
引用計數(shù)(Reference Count)喉钢,也叫保留計數(shù)(retain count),表示對象被引用的次數(shù)良姆。一個簡單而有效的管理對象生命周期的方式肠虽。
3.引用計數(shù)的工作原理:
當(dāng)我們創(chuàng)建(alloc)一個新對象A的時候,它的引用計數(shù)從零變?yōu)?1玛追;
當(dāng)有一個指針指向這個對象A税课,也就是某對象想通過引用保留(retain)該對象A時帽哑,引用計數(shù)加 1琐鲁;
當(dāng)某個指針/對象不再指向這個對象A,也就是釋放(release)該引用后损谦,我們將其引用計數(shù)減 1陆馁;
當(dāng)對象A的引用計數(shù)變?yōu)?0 時找颓,說明這個對象不再被任何指針指向(引用)了,這個時候我們就可以將對象A銷毀叮贩,所占內(nèi)存將被回收击狮,且所有指向該對象的引用也都變得無效了。系統(tǒng)也會將其占用的內(nèi)存標(biāo)記為“可重用”(reuse)益老;
流程參考圖如下:
(圖片表格取自《編寫高質(zhì)量iOS與OS X代碼的52個有效方法》一書)
4.操作引用計數(shù)的那些方法:
A.以下是NSObject協(xié)議中聲明的3個用于操作計數(shù)器的方法:
retain : 保留彪蓬。保留計數(shù)+1;
release : 釋放杨箭。保留計數(shù) -1寞焙;
autorelease :稍后(清理“自動釋放池”時),再遞減保留計數(shù)互婿,所以作用是延遲對象的release;
B.dealloc方法:
另外辽狈,當(dāng)計數(shù)為0的時候?qū)ο髸詣诱{(diào)用dealloc慈参。而我們可以在dealloc方法做的,就是釋放指向其他對象的引用刮萌,以及取消已經(jīng)訂閱的KVO驮配、通知;(自己不能調(diào)用dealloc方法着茸,因?yàn)檫\(yùn)行期系統(tǒng)會在恰當(dāng)?shù)臅r候調(diào)用它壮锻,而且一旦調(diào)用dealloc方法,對象不再有效涮阔,即使后續(xù)方法再次調(diào)用retain猜绣。)
所以,調(diào)用release后會有2種情況:
調(diào)用前計數(shù)>1敬特,計數(shù)減1掰邢;
調(diào)用前計數(shù)<1牺陶,對象內(nèi)存被回收;
C.retainCount:獲取引用計數(shù)的方法辣之。
Eg: [object retainCount]; //得到object的引用計數(shù)
retain掰伸、autorelease、release方法詳解
retain作用:
調(diào)用后計數(shù)+1怀估,保留對象操作狮鸭。但是當(dāng)對象被銷毀、內(nèi)存被回收的時候多搀,即使使用retain也不再有效怕篷;
autorelease作用:
autorelease不立即釋放,而是注冊到autoreleasepool(自動釋放池)中酗昼,等到pool結(jié)束時釋放池再自動調(diào)用release進(jìn)行釋放工作廊谓。
autorelease看上去很像ARC,但是實(shí)際上更類似C語言中的自動變量(局部變量)麻削,當(dāng)某自動變量超出其作用域(例如大括號)蒸痹,該自動變量將被自動廢棄,而autorelease中對象實(shí)例的release方法會被調(diào)用呛哟;[與C不同的是叠荠,開發(fā)者可以設(shè)定變量的作用域。]
釋放時間:每個Runloop中都創(chuàng)建一個Autorelease pool(自動釋放池)扫责,每一次的Autorelease榛鼎,系統(tǒng)都會把該Object放入了當(dāng)前的Autorelease pool中,并在Runloop的末尾進(jìn)行釋放鳖孤,而當(dāng)該pool被釋放時者娱,該pool中的所有Object會被調(diào)用Release。 所以苏揣,一般情況下黄鳍,每個接受autorelease消息的對象,都會在下個Runloop開始前被釋放平匈。
例如可用以下場景:(需要從ARC改為使用手動管理的可以做如下的設(shè)置: 在Targets的Build Phases選項(xiàng)下Compile Sources下選擇要不使用ARC編譯的文件框沟,雙擊它,輸入-fno-objc-arc即可使用MRC手工管理內(nèi)存方式增炭;)
-(NSString *)getSting{
NSString *str = [[NSString alloc]initWithFormat:@"I am Str"];
return [str autorelease];
}
自動釋放池中的釋放操作會等到下一次時間循環(huán)時才會執(zhí)行忍燥,所以調(diào)用以下:
NSString *str = [self getSting];NSLog(@"%@",str);
返回的str對象得以保留,延遲釋放隙姿。因此可以無需再NSLog語句之前執(zhí)行保留操作梅垄,就可以將返回的str對象輸出。
所以可見autorelease的作用是能延長對象的生命期孟辑。使其在跨越方法調(diào)用邊界后依然可以存活一段時間哎甲。
release作用:
release會立即執(zhí)行釋放操作蔫敲,使得計減1;
有這樣一種情況:當(dāng)某對象object的引用計數(shù)為1的時候炭玫,調(diào)用“[object release];”奈嘿,此時如果再調(diào)用NSLog方法輸出object的話,可能程序就會崩潰吞加,當(dāng)然只是有可能裙犹,因?yàn)閷ο笏純?nèi)存在“解除分配(deallocated)”之后,只是放回“可用內(nèi)存池(avaiable pool)”衔憨,但是如果執(zhí)行NSLog時叶圃,尚未覆寫對象內(nèi)存,那么該對象依然有效践图,所以程序有可能不會崩潰掺冠,由此可見,因過早地釋放對象而導(dǎo)致的bug很難調(diào)試码党。
為避免這種情況德崭,一般調(diào)用完對象之后都會清空指針:"object = nil",這樣就能保證不會出現(xiàn)指向無效對象的指針揖盘,也就是懸掛指針(dangling pointer);
懸掛指針:指向無效對象的指針眉厨。
那么,向已經(jīng)釋放(dealloc)的對象發(fā)送消息兽狭,retainCount會是多少憾股?
原則是不可以這么做。因?yàn)樵搶ο蟮膬?nèi)存已經(jīng)被回收箕慧,而我們向一個已經(jīng)被回收的對象發(fā)了一個 retainCount 消息服球,所以它的輸出結(jié)果應(yīng)該是不確定的,例如為減少一次內(nèi)存的寫操作销钝,不將這個值從 1 變成 0有咨,所以很大可能輸出1。例如下面這種情況:
Person *person = [[Person alloc] init]; //此時蒸健,計數(shù) = 1
[person retain]; //計數(shù) = 2
[person release]; //計數(shù) = 1
[person release]; //很可能計數(shù) = 1;
雖然第四行代碼把計數(shù)1release了一次,原理上person對象的計數(shù)會變成0婉商,但是實(shí)際上為了優(yōu)化對象的釋放行為似忧,提高系統(tǒng)的工作效率,在retainCount為1時release系統(tǒng)會直接把對象回收丈秩,而不再為它的計數(shù)遞減為0盯捌,所以一個對象的retainCount值有可能永遠(yuǎn)不為0;
因此蘑秽,不管是否為ARC的開發(fā)環(huán)境中饺著,也不推薦使用retainCount來做為一個對象是否存在于內(nèi)存之中的依據(jù)箫攀。
<br /><br />
二、ARC
1.背景:
ARC是iOS 5推出的新功能幼衰,全稱叫 ARC(Automatic Reference Counting)靴跛。
即使2014 年的 WWDC 大會上推出的Swift 語言,該語言仍然使用 ARC 技術(shù)作為其管理方式渡嚣。
2.ARC是什么梢睛?
需要注意的是,ARC并不是GC(Garbage Collection 垃圾回收器)识椰,它只是一種代碼靜態(tài)分析(Static Analyzer)工具绝葡,背后的原理是依賴編譯器的靜態(tài)分析能力,通過在編譯時找出合理的插入引用計數(shù)管理代碼腹鹉,從而提高iOS開發(fā)人員的開發(fā)效率藏畅。
Apple的文檔里是這么定義ARC的:
“自動引用計數(shù)(ARC)是一個編譯器級的功能,它能簡化Cocoa應(yīng)用中對象生命周期管理(內(nèi)存管理)的流程功咒∮溲郑”
3.ARC在做什么?
在編譯階段航瞭,編譯器將在項(xiàng)目代碼中自動為分配對象插入retain诫硕、release和autorelease,且插入的代碼不可見刊侯。
但是章办,需要注意的是,ARC模式下引用計數(shù)規(guī)則還起作用滨彻,只是編譯器會為開發(fā)者分擔(dān)大部分的內(nèi)存管理工作藕届,除了插入上述代碼,還有一部分優(yōu)化以及分析內(nèi)存的管理工作亭饵。
作用:
a.降低內(nèi)存泄露等風(fēng)險 休偶;
b.減少代碼工作量,使開發(fā)者只需專注于業(yè)務(wù)邏輯辜羊;
4.ARC具體為引用計數(shù)做了哪些工作踏兜?
編譯階段自動添加代碼:
編譯器會在編譯階段以恰當(dāng)?shù)臅r間與地方給我們填上原本需要手寫的retain、release八秃、autorelease等內(nèi)存管理代碼碱妆,所以ARC并非運(yùn)行時的特性,也不是如java中的GC運(yùn)行時的垃圾回收系統(tǒng)昔驱;因此疹尾,我們也可以知道,ARC其實(shí)是處于編譯器的特性。
例如:
-(void)setup{
_person = [person new];
}
在手工管理內(nèi)存的環(huán)境下纳本,_person是不會自動保留其值窍蓝,而在ARC下編譯,其代碼會變成:
-(void)setup{
person *tmp = [person new];
_person = [tmp retain];
[tmp release];
}
當(dāng)然繁成,在開發(fā)工作中吓笙,retain和release對于開發(fā)人員來說都可以省去,由ARC系統(tǒng)自動補(bǔ)全朴艰,達(dá)到同樣的效果观蓄。
但實(shí)際上,ARC系統(tǒng)在自動調(diào)用這些方法時祠墅,并不通過普通的Objective-C消息派發(fā)控制侮穿,而是直接調(diào)用底層C語言的方法:
比如retain,ARC在分析到某處需要調(diào)用保留操作的地方毁嗦,調(diào)用了與retain等價的底層函數(shù) objc_retain亲茅,所以這也是ARC下不能覆寫retain、release或者autorelease的原因狗准,因?yàn)檫@些方法在ARC從來不會被直接調(diào)用克锣。
運(yùn)行期組件的優(yōu)化:
ARC是編譯器的特性,但也包含了運(yùn)行期組件腔长,所執(zhí)行的優(yōu)化很有意義袭祟。
例子:
person工廠方法personWithName可以得到一個person對象,在這里調(diào)用并賦值給person的一個實(shí)例_one:
_one = [person personWithName:@"name"];
可能會出現(xiàn)這種情況:
在personWithName方法中捞附,返回對象給_one之前巾乳,為其調(diào)用了一次autorelease方法。
由于實(shí)例變量是個強(qiáng)引用鸟召,所以編譯器會在設(shè)置其值的時候還需要執(zhí)行一次保留操作胆绊。
//在personWithName方法返回前已有調(diào)用一次autorelease方法進(jìn)行保留操作;
person *tmp = [person personWithName:@"name"];
_one = [tmp retain];
很明顯欧募,autorelease與緊跟其后的retain是重復(fù)的压状。為提升性能,可以將二者刪去跟继,舍棄autorelease這個概念种冬,并且規(guī)定返回對象的技術(shù)都比期望值多1,但是為了向后兼容非ARC等情況.
ARC采取了另外一種方式:
ARC可以在運(yùn)行期檢測到這一對多余的操作舔糖。所以在返回對象時碌廓,不直接調(diào)用autorelease,改為調(diào)用objc_autoreleaseReturnValue剩盒,用來檢測返回之后即將要執(zhí)行的代碼中,含有retain操作,則設(shè)置全局?jǐn)?shù)據(jù)結(jié)構(gòu)(此數(shù)據(jù)結(jié)構(gòu)具體內(nèi)容因處理器而異)中的一個標(biāo)志位辽聊,而不執(zhí)行autorelease操作纪挎。
同樣,若方法返回一個自動釋放對象跟匆,調(diào)用personWithName方法的代碼段不執(zhí)行retain异袄,改為執(zhí)行objc_retainAutoreleaseReturnValue函數(shù)。此函數(shù)檢測剛才的那個標(biāo)志位玛臂,若已經(jīng)置位了烤蜕,則不執(zhí)行retain操作。
而設(shè)置并檢測標(biāo)志位迹冤,要比調(diào)用autorelease和retain更快讽营,這就使得這一情況的處理得到優(yōu)化。
修改2個函數(shù)后優(yōu)化完整結(jié)果如下: 【例子來自《編寫高質(zhì)量iOS與OS X代碼的52個有效方法》一書P126】
我們可以通過兩個函數(shù)的偽代碼大致描述如下:
像是objc_autoreleaseReturnValue這個函數(shù)是如何檢測方法調(diào)用者是否會立刻保留對象呢泡徙,這就要交給處理器來解決了橱鹏。
由于必須查看原始機(jī)器碼指令方可判斷出這一點(diǎn)需要處理器來定。
所以堪藐,其實(shí)只有編譯器的作者才能知道這里是如何實(shí)現(xiàn)此函數(shù)的莉兰。
ARC的安全性:
在編寫屬性的設(shè)置方法(setter)時,如果使用手工管理方式礁竞,可能會需要如下編寫:
-(void)setObject:(id)object{
[_object release];
_object = [object retain];
}
但是這樣寫會出現(xiàn)問題:如果說新值object和實(shí)例變量_object的值是相同的糖荒,而且只有當(dāng)前實(shí)例變量對象還在引用這個值,那么設(shè)置方法中的釋放操作會使得該值保留計數(shù)為0模捂,系統(tǒng)將其回收捶朵,所以接下來的保留操作,將會令應(yīng)用程序崩潰枫绅。而在使用ARC的環(huán)境下泉孩,就不可能會發(fā)送這樣的的“邊界情況”了:
剛才的代碼在ARC下可以這樣寫:(當(dāng)然,我們知道如果不需要覆寫setter方法并淋,也可以不編寫此方法寓搬,直接使用"self.object = xxx"也可以安全地調(diào)用。):
-(void)setObject:(id)object{
_object = object;
}
而且ARC會用一種安全的方式來設(shè)置:先保留新值县耽,再釋放舊值句喷,最后設(shè)置實(shí)例變量。
在手工管理的情況下兔毙,我們需要特別注意這種"邊緣情況"唾琼,但是ARC下,我們就可以很輕松地編寫這種代碼了澎剥,而不用去考慮這種情況如何處理了锡溯。
總結(jié):將內(nèi)存管理交由編譯器和運(yùn)行期組件來做,可以使代碼得到多種優(yōu)化,而上面是其中一種方式祭饭。
5.ARC下需要注意的規(guī)則
a.關(guān)于dealloc:
. 不能顯式調(diào)用dealloc芜茵;
. 不能再dealloc中調(diào)用【super dealloc】(非ARC下則需要調(diào)用.);
. 不能在dealloc 中釋放資源(非ARC下需要釋放不同的對象)倡蝙;
b.以及九串,不能顯式調(diào)用以下代碼:
c.不能再使用NSAutoreleasePool對象,ARC提供了@autoreleasepool塊來代替它寺鸥,這樣更有效率猪钮;
6.所有權(quán)修飾符
oc編程中為了處理對象,可將變量類型定義為id類型或各種對象類型胆建。使用這些限定符可以確切地聲明對象變量和屬性的生命周期烤低;
所謂對象類型就是指向NSObject這樣的oc類的指針,例如“NSObject *”眼坏。id類型用于隱藏對象類型的類名部分拂玻。相當(dāng)于C語言中常用的“void *”;
ARC下宰译,id類型和對象類型上必須附加所有權(quán)修飾符檐蚜;
所有權(quán)修飾符一共有4種:
__strong:
強(qiáng)引用,可以引用別的對象為強(qiáng)引用沿侈,相當(dāng)于retain的特性闯第;表明變量持有alloc/new/copy/mutableCopy方法群創(chuàng)建的對象的強(qiáng)引用,強(qiáng)引用變量會在其作用域里被保留缀拭,在超出作用域后被釋放咳短,為默認(rèn)的修飾符;
例如以下代碼:
id objc = [[NSObject alloc] init];
實(shí)際上已被附上所有權(quán)修飾符:
id __strong objc = [[NSObject alloc] init];
__weak:
使用__strong蛛淋,有可能2個對象相互強(qiáng)引用或者1個對象對自身強(qiáng)引用則會發(fā)生循環(huán)引用(如下圖咙好,或者叫保留環(huán)),所以當(dāng)對象在超出其生存周期后褐荷,本應(yīng)被系統(tǒng)廢棄卻仍然被引用者所持有勾效,所以造成內(nèi)存泄露(應(yīng)當(dāng)廢棄的對象在超出生命周期后,繼續(xù)存在)叛甫;
而當(dāng)我們對可能會發(fā)送循環(huán)引用的對象進(jìn)行__weak弱引用修飾层宫,弱引用變量不會持有對象,且生成的對象會立刻釋放其监,可避免循環(huán)引用萌腿,并且弱引用還有另外一個特點(diǎn),若對象被系統(tǒng)回收抖苦,該弱引用變量將自動失效并且賦值為nil毁菱。
__unsafe_unretained: 不安全的所有權(quán)修飾符米死,ARC的內(nèi)存管理是編譯器的工作,而附有__unsafe_unretained修飾符的變量不屬于編譯器的內(nèi)存管理對象鼎俘。與__weak作用一樣哲身,也可以避免循環(huán)引用;但是不同的是贸伐,__unsafe_unretained屬性的變量不會將變量設(shè)置為nil,而是就處于于懸掛狀態(tài)怔揩;
__autoreleasing:在ARC中使用“@autoreleasepool塊”來取代“NSAutoreleasePool”類對象的生成捉邢,通過將對象賦值給附加了__autoreleasing修飾符的變量來替代調(diào)用autorelease方法;
Other:ARC需要注意的事項(xiàng)商膊?
1.過度使用 block 之后伏伐,無法解決循環(huán)引用問題。
2.遇到底層 Core Foundation 對象晕拆,需要自己手工管理它們的引用計數(shù)時藐翎,我們需轉(zhuǎn)換關(guān)鍵字,作為橋接轉(zhuǎn)換以解決 Core Foundation 對象與 Objective-C 對象相對轉(zhuǎn)換的問題:
__bridge:使用__bridge標(biāo)記可以在不修改相關(guān)對象的引用計數(shù)的情況下实幕,將對象從Core Foundation框架數(shù)據(jù)類型轉(zhuǎn)換為Foundation框架數(shù)據(jù)類型(反之亦然)吝镣。
__bridge_retained:會將相關(guān)對象的引用計數(shù)加 1,并且可以將Core Foundation框架數(shù)據(jù)類型對象轉(zhuǎn)換為Foundation框架數(shù)據(jù)類型對象昆庇,并從ARC接管對象的所有權(quán)末贾。
__bridge_transfer:可以將Foundation框架數(shù)據(jù)類型對象轉(zhuǎn)換為Core Foundation框架數(shù)據(jù)類型對象,并且會將對象的所有權(quán)交給ARC管理整吆,也就是說引用計數(shù)交由ARC管理拱撵;
總結(jié):就推薦2本經(jīng)典的書(估計很多人早就看完了?? ),書本也好表蝙,pdf也好拴测,建議看一下:
《Effective Objective-C 2.0 編寫高質(zhì)量iOS與OS X代碼的52個有效方法》
《Objective-C高級編程 iOS與OS X多線程和內(nèi)存管理》
<br />
(轉(zhuǎn)載請標(biāo)明原文出處,謝謝支持 ~ - ~)
? by:啊左~