iOS開發(fā)讀書筆記:Objective-C高級(jí)編程 iOS與OS X多線程和內(nèi)存管理-上篇(自動(dòng)引用計(jì)數(shù))
iOS開發(fā)讀書筆記:Objective-C高級(jí)編程 iOS與OS X多線程和內(nèi)存管理-中篇(Blocks)
iOS開發(fā)讀書筆記:Objective-C高級(jí)編程 iOS與OS X多線程和內(nèi)存管理-下篇(GCD)
本章主要介紹從OS X Lion和iOS 5 引入的內(nèi)存管理新功能---自動(dòng)引用計(jì)數(shù)。
目錄
- 1.1 什么是自動(dòng)引用計(jì)數(shù)
- 1.2 內(nèi)存管理/引用計(jì)數(shù)
- 1.2.1 概要
- 1.2.2 內(nèi)存管理的思考方式
- 1.2.3 alloc/retain/release/dealloc 實(shí)現(xiàn)
- 1.2.4 蘋果的實(shí)現(xiàn)
- 1.2.5 autorelease
- 1.2.6 autorelease 實(shí)現(xiàn)
- 1.2.7 蘋果的實(shí)現(xiàn)
- 1.3 ARC規(guī)則
- 1.3.1 概要
- 1.3.2 內(nèi)存管理的思考方式
- 1.3.3 所有權(quán)修飾符
- 1.3.4 規(guī)則
- 1.3.5 屬性
- 1.3.6 數(shù)組
- 1.4 ARC的實(shí)現(xiàn)
- 1.4.1 __strong修飾符
- 1.4.2 __weak修飾符
- 1.4.3 __autoreleasing修飾符
- 1.4.4 引用計(jì)數(shù)
1.1 什么是自動(dòng)引用計(jì)數(shù)
顧名思義,自動(dòng)引用計(jì)數(shù)(ARC,Automatic Reference Counting) 是指內(nèi)存管理中對(duì)引用采取自動(dòng)計(jì)數(shù)的技術(shù)。讓編譯器來進(jìn)行內(nèi)存管理。無需再次鍵入retain/release代碼,可降低程序崩潰、內(nèi)存泄露等風(fēng)險(xiǎn),減少程序員工作量。編譯器完全清楚目標(biāo)對(duì)象,并能立刻釋放那些不再使用的對(duì)象。如此一來,應(yīng)用程序?qū)⒕哂锌深A(yù)測(cè)性,且能流暢運(yùn)行,速度也將大幅提升。
1.2 內(nèi)存管理/引用計(jì)數(shù)
1.2.1 概要
略
1.2.2 內(nèi)存管理的思考方式
首先來學(xué)習(xí)引用計(jì)數(shù)式內(nèi)存管理的思考方法:
- 自己生成的對(duì)象,自己持有;
- 非自己生成的對(duì)象,自己也能持有;
- 不再需要自己持有的對(duì)象時(shí),釋放;
- 非自己持有的對(duì)象,無法釋放;
上文出現(xiàn)了“生成”、“持有”、“釋放”三個(gè)詞。還要加上“廢棄”一詞。各個(gè)次表示的OC方法如下:
| 對(duì)象操作 | OC方法 |
|---|---|
| 生成并持有對(duì)象 | alloc/new/copy/mutableCopy等 |
| 持有對(duì)象 | retain |
| 釋放對(duì)象 | release |
| 廢棄對(duì)象 | dealloc |
這些有關(guān)OC內(nèi)存管理的方法,實(shí)際上不包括在該語言中,而是包括在Cocoa框架中用于OS X、iOS應(yīng)用開發(fā)。Cocoa框架中Foundation框架類庫的NSObject類單幅內(nèi)存管理的職責(zé)。OC內(nèi)存管理中的alloc/retain/release/dealloc 方法分別指代NSObject類的alloc類方法、retain實(shí)例方法、release實(shí)例方法和dealloc實(shí)例方法。
自己生成的對(duì)象,自己持有
使用以下名稱開頭的方法名意味著自己生成的對(duì)象只有自己持有:
- alloc
- new
- copy
- mutableCopy
本文所說的自己將之理解為編程人員“自身”。下面寫出了自己生成并持有對(duì)象的源代碼,為生成并持有對(duì)象,我們使用alloc方法。
id obj = [[NSObject alloc] init]; //自己生成并持有對(duì)象
使用NSObject類的alloc類方法就能讓自己生成并持有對(duì)象。指向生成并持有對(duì)象的還真被賦給變量obj。另外,使用如下new類方法也能生成并持有對(duì)象。[[NSObject alloc] init] 與 [NSObject new]是完全一致的。
id obj = [NSObject new]; //自己生成并持有對(duì)象
copy方法利用基于NSCopying方法約定,由各類實(shí)現(xiàn)的copyWithZone;方法生成并持有對(duì)象的副本。與copy方法類似,mutableCopy方法利用基于NSMutableCopying方法約定,由各類實(shí)現(xiàn)的mutableCopyWithZone:方法生成并持有對(duì)象的副本。兩者的區(qū)別在于,copy方法生成不可變更的對(duì)象,而mutableCopy方法生成可變更的對(duì)象。這類似于NSArray類對(duì)象與NSMutableArray類對(duì)象的差異。用這些方法生成的對(duì)象,雖然是對(duì)象的副本,但同alloc、new方法一樣,在“自己生成并持有對(duì)象”這點(diǎn)上沒有改變。
另外,根據(jù)上述“使用以下名稱開頭的方法名”,下列名稱也意味著自己生成并持有對(duì)象。
- allocMyObject
- newThatObject
- copyThis
- mutableCopyYourObject
但是對(duì)于以下名稱,既使用alloc/new/copy/mutableCopy名稱開頭,并不屬于同一類別的方法。
- allocate
- newer
- copying
- mutableCopyed
這里用駝峰拼寫法來命名。
非自己生成的對(duì)象,自己也能持有
用上述項(xiàng)目之外的方法取得的對(duì)象,既用alloc/new/copy/mutableCopy以外的方法取得的對(duì)象,因?yàn)榉亲约荷刹⒊钟?,所以自己不是該?duì)象的持有者,我們來使用alloc/new/copy/mutableCopy以外的方法看看,這里試用一下NSMutableArray類的array類方法。
id obj = [NSMutableArray array];//取得非自己生成并持有的對(duì)象:取得的對(duì)象存在,但自己不持有對(duì)象。
該源代碼中,NSMutableArray類對(duì)象被賦給變量obj,但變量obj自己并不持有該對(duì)象,使用retain方法可以持有對(duì)象。
id obj = [NSMutableArray array];//取得非自己生成并持有的對(duì)象:取得的對(duì)象存在,但自己不持有對(duì)象。
[obj retain]; //自己持有對(duì)象
不再需要自己持有的對(duì)象時(shí)釋放
自己持有的對(duì)象,一旦不再需要,持有者有義務(wù)釋放該對(duì)象。釋放使用release方法。
id obj = [[NSObject alloc] init]; //自己生成并持有對(duì)象
[obj release];//釋放對(duì)象:指向?qū)ο蟮穆氊?zé)仍然被保留在變量obj中,貌似能夠訪問,但對(duì)象一經(jīng)釋放絕對(duì)不可訪問。
自己生成而非自己持有的對(duì)象,若用retain方法變?yōu)樽约撼钟?,也同樣可以用release方法釋放。
id obj = [NSMutableArray array]; //取得非自己生成并持有對(duì)象
[obj retain];//自己持有對(duì)象
[obj release];//釋放對(duì)象,對(duì)象不可再被訪問。
用alloc/new/copy/mutableCopy方法生成并持有的對(duì)像,或者用retain方法持有的對(duì)象,一旦不再需要,務(wù)必要用release方法進(jìn)行釋放。
如果要用某個(gè)方法生成對(duì)象,并將其返回給該方法的調(diào)用方,那么它的源代碼又是怎樣的呢?
- (id)allocObject {
id obj = [[NSObject alloc] init]; //自己生成并持有對(duì)象
return obj;
}
如上例所示,原封不動(dòng)的返回用alloc方法生成并持有的對(duì)象,就能讓調(diào)用放也持有該對(duì)象。請(qǐng)注意allocObject這個(gè)名稱是符合前文命名規(guī)則的。
id obj1= [obj0 allocObject];
allocObject名稱符合前文的命名規(guī)則,因此它與用alloc方法生成并持有對(duì)象情況完全相同,所以使用allocObject方法也意味著“自己生成并持有對(duì)象”。
那么,調(diào)用[NSMutableArray array]方法使取得的對(duì)象存在,但自己不持有對(duì)象,又是如何實(shí)現(xiàn)的呢?根據(jù)上文命名規(guī)則,不能使用以alloc/new/copy/mutableCopy開頭的方法名,因此要使用object這個(gè)方法名。
- (id)object {
id obj = [[NSObject alloc] init]; //自己持有對(duì)象
[obj autorelease];//取得的對(duì)象存在,但自己不持有對(duì)象
return obj;
}
上例中,我們使用了autorelease方法。用該方法,可以使取得的對(duì)象存在,但自己不持有對(duì)象。autorelease提供這樣的功能,使對(duì)象在超出指定的生存范圍時(shí)能夠自動(dòng)并正確的示范(調(diào)用release方法)。
id obj1= [obj0 object];
使用NSMutableArray類的array類方法等可以取得誰都不持有的對(duì)象,這些方法都是通過autorelease而實(shí)現(xiàn)的。此外,根據(jù)上文的命名規(guī)則,這些用來取得誰都不持有的對(duì)象的方法名不能以alloc/new/copy/mutableCopy開頭,這點(diǎn)需要注意。
當(dāng)然也能通過retain方法將調(diào)用autorelease方法取得的對(duì)象變?yōu)樽约撼钟小?/p>
id obj1 = [obj0 object];//取得的對(duì)象存在,但自己不持有對(duì)象
[obj1 retain];// 自己持有對(duì)象
無法釋放非自己持有的對(duì)象
對(duì)于用alloc/new/copy/mutableCopy方法生成并持有的對(duì)象,或是用retain方法持有的對(duì)象,由于持有者是自己,所以在不需要該對(duì)象時(shí)需要將其釋放。而由此以外所得到的對(duì)象絕對(duì)不能釋放。倘若在營業(yè)程序中釋放了非自己所持有的對(duì)象就會(huì)造成崩潰。例如自己生成并持有對(duì)象后,在釋放完不再需要的對(duì)象之后再次釋放。
比如:釋放之后再次釋放已非自己持有的對(duì)象,應(yīng)用程序崩潰!
原因:再度廢棄已經(jīng)廢棄了的對(duì)象時(shí)崩潰,訪問已經(jīng)廢棄的對(duì)象的崩潰
或者在“取得的對(duì)象存在,但自己不持有對(duì)象”時(shí)釋放
id obj1 = [obj0 object];//取得的對(duì)象存在,但自己不持有對(duì)象
[obj1 release];// 釋放了非自己持有的對(duì)象,這肯定會(huì)導(dǎo)致應(yīng)用程序崩潰!
如這些例子所示,釋放非自己持有的對(duì)象會(huì)造成程序崩潰。因此絕對(duì)不要去釋放非自己持有的對(duì)象。
以上四項(xiàng)內(nèi)容,就是“引用計(jì)數(shù)式內(nèi)存管理”的思考方式。
1.2.3 alloc/retain/release/dealloc 實(shí)現(xiàn)
接下來,以O(shè)C內(nèi)存管理使用的alloc/retain/release/dealloc方法為基礎(chǔ),通過實(shí)際操作來理解內(nèi)存管理。
Foundation框架并沒有公開,但是Foundation使用的Core Foundation框架的源代碼,以及通過調(diào)用NSObject類進(jìn)行內(nèi)存管理部分的源代碼是公開的。但是,沒有NSObject類的源代碼,就很難了解NSObject類的內(nèi)部實(shí)現(xiàn)細(xì)節(jié)。為此,我們首先使用開源軟件GNUstep來說明。GNUstep是Cocoa框架的互換框架,它的源代碼雖不能說與蘋果的Cocoa實(shí)現(xiàn)完全相同,但是從使用者角度來看,非常相似。我們來看看GNUstep源代碼中NSObject類的alloc類方法。
NSObject類的alloc類方法
id obj = [NSObject alloc];
上述調(diào)用NSObject類的alloc類方法在NSObject.m源代碼中的實(shí)現(xiàn)如下:
+ (id)alloc {
return [self allocWithZone:NSDefaultMallocZone()];
}
+ (id) allocWithZone:(NSZone*)z {
return NSAllocateObject(self,0,z);
}
通過allocWithZone:類方法調(diào)用NSAllocateObject函數(shù)分配了對(duì)象。下面看看NSAllocateObject函數(shù)
struct obj_layout {
NSUinteger retained;
}
inline id
NSAllocateObject (Class aClass,NSUinteger extraBytes,NSZone *zone) {
int size = 計(jì)算容納對(duì)象所需內(nèi)存大小;
id new = NSZoneMalloc(zone,size);
memset(new,0,size);
new = (id)&((struct obj_layout *)new)[1];
}
NSAllocateObject函數(shù)通過調(diào)用NSZongMalloc函數(shù)來分配存放對(duì)象所需的內(nèi)存空間,之后將該內(nèi)存空間置0,最好返回作為對(duì)象而使用的指針。
NSDefaultMallocZone、NSZoneMalloc 等名稱中包含的NSZone是什么呢?它時(shí)為防止內(nèi)存碎片化而引入的結(jié)構(gòu)。對(duì)內(nèi)存分配的區(qū)域本身進(jìn)行多重化管理,根據(jù)使用對(duì)象的目的,對(duì)象的大小分配內(nèi)存,從而提高了內(nèi)存管理的效率。
但是,現(xiàn)在的運(yùn)行時(shí)系統(tǒng)之上簡單忽略了區(qū)域的概念。運(yùn)行時(shí)系統(tǒng)中的內(nèi)存管理本身已極具效率,使用曲藝來管理內(nèi)存反而會(huì)引起內(nèi)存使用效率低下以及源代碼復(fù)雜化等問題。
alloc類方法用struct_obj_layout中的retained整數(shù)來保存引用計(jì)數(shù),并將其寫入對(duì)象內(nèi)存頭部,該對(duì)象內(nèi)存塊全部置0后返回。
[缺圖]
對(duì)象的引用計(jì)數(shù)可通過retainCount實(shí)例方法取得:
[obj retainCount];
由對(duì)象尋址到對(duì)象內(nèi)存頭部,從而訪問其中的retained變量。
[缺圖]
alloc/retain/release/dealloc 具體總結(jié)如下:
- 在Objective-C的對(duì)象中存有引用計(jì)數(shù)這一整數(shù)值。
- 調(diào)用alloc或是retain方法后,引用計(jì)數(shù)值+1。
- 調(diào)用release后,引用計(jì)數(shù)值-1。
- 引用計(jì)數(shù)值為0,調(diào)用dealloc方法廢棄對(duì)象。
1.2.4 蘋果的實(shí)現(xiàn)
在看了GNUstep中的內(nèi)存管理和引用計(jì)數(shù)的實(shí)現(xiàn)后,我們來看看蘋果的實(shí)現(xiàn)。
retainCount、retain、release等函數(shù),蘋果的實(shí)現(xiàn)大概就是采用散列表(引用計(jì)數(shù)表)來管理引用計(jì)數(shù)。
GNUstep將引用計(jì)數(shù)保存在對(duì)象占用內(nèi)存塊頭部的變量中,而蘋果的實(shí)現(xiàn),則是保存在引用計(jì)數(shù)表的記錄中。
通過內(nèi)存塊頭部管理引用計(jì)數(shù)的好處如下:
- 少量代碼即可完成
- 能夠統(tǒng)一管理引用計(jì)數(shù)用內(nèi)催款與對(duì)象用內(nèi)存塊
通過引用計(jì)數(shù)表管理引用計(jì)數(shù)的好處如下:
- 對(duì)象用內(nèi)存塊的分配無需考慮內(nèi)存塊頭部
- 引用計(jì)數(shù)表各記錄中存有內(nèi)存地址,可從各個(gè)記錄追溯到各對(duì)象的內(nèi)存塊
這里特別要說的是,第二條這一特性在調(diào)試時(shí)有著舉足輕重的作用。即使出現(xiàn)故障導(dǎo)致對(duì)象占用的內(nèi)存塊損壞,但只要引用計(jì)數(shù)表沒有被損壞,就能夠確認(rèn)各內(nèi)存塊的位置。
另外,在利用工具檢測(cè)內(nèi)存泄露是,引用計(jì)數(shù)表的各記錄也有助于檢測(cè)各對(duì)象的持有者是否存在。
1.2.5 autorelease
顧名思義,aotorelease就是自動(dòng)釋放。這看上去很ARC,但實(shí)際上它更類似于C語言中自動(dòng)變量(局部變量)的特性。
來復(fù)習(xí)下C語言的自動(dòng)變量。程序執(zhí)行時(shí),若某自動(dòng)變量超出其作用域,該自動(dòng)變量將被自動(dòng)廢棄。
autorelease會(huì)像C語言的自動(dòng)變量那樣來對(duì)待對(duì)象實(shí)例。當(dāng)超出其作用域(相當(dāng)于變量作用域時(shí)),對(duì)象實(shí)例的releas實(shí)例方法被調(diào)用。另外,同C語言的自動(dòng)變量不同的是,編程人員可以設(shè)定變量的作用域。
autorelease的具體使用方法如下:
- 生成并持有NSAutoreleasePool對(duì)象。
- 調(diào)用已分配對(duì)象的autorelease實(shí)例方法。
- 廢棄NSAutoreleasePool對(duì)象。
NSAutoreleasePool對(duì)象的生存周期相當(dāng)于C語言變量的作用域。對(duì)于所有調(diào)用過autorelease實(shí)例方法的對(duì)象,在廢棄NSAutoreleasePool對(duì)象時(shí),都將調(diào)用release實(shí)例方法。
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];
上述源代碼中最后一行的“[pool drain]”等同于“[obj release]”;
在Cocoa框架中,相當(dāng)于程序煮循環(huán)的NSRunLoop或者在其他程序可運(yùn)行的地方,對(duì)NSAutoreleasePool對(duì)象進(jìn)行生成、持有和廢棄處理。因此,應(yīng)用程序開發(fā)者不一定非得使用NSAutoreleasePool對(duì)象來進(jìn)行開發(fā)工作。
盡管如此,但在大量產(chǎn)生autorelease的對(duì)象時(shí),只要不廢棄NSAutoreleasePool對(duì)象,那么生成的對(duì)象就不能被釋放,因此有時(shí)會(huì)產(chǎn)生內(nèi)存不足的現(xiàn)象。
for (int i = 0; i<10000; i++) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// 大量產(chǎn)生autorelease的對(duì)象
[pool drain]; // autorelease的對(duì)象被遺棄release
}
另外,Cocoa框架中也有很對(duì)類方法用于返回autorelease的對(duì)象。比如NSMutableArray類的arrayWithCapacity類方法。
id array = [NSMutableArray arrayWithCapacity:1];
等同于
id array = [[[NSMutableArray alloc] initWithCapacity:1] autorelease];
1.2.6 autorelease 實(shí)現(xiàn)
autorelease 實(shí)例方法的本質(zhì)是調(diào)用NSAutoreleasePool對(duì)象的addObejct類方法。
如果調(diào)用NSObject類的Autorelease實(shí)例方法,該對(duì)象將被追加到正在使用的NSAutoreleasePool對(duì)象中的數(shù)組里。
以下為通過drain實(shí)例方法廢棄正在使用的NSAutoreleasePool對(duì)象的過程
- (void)drain {
[self dealloc];
}
- (void) dealloc {
[self emptyPool];
[array release];
}
- (void) emptyPool {
for (id obj in array) {
[obj release];
}
}
雖然調(diào)用了好幾個(gè)方法,但可以確定對(duì)于數(shù)組中的所有對(duì)象都調(diào)用了release實(shí)例方法。
1.2.7 蘋果的實(shí)現(xiàn)
如果autorelease NSAutoreleasePool對(duì)象會(huì)如何?
NSAutoreleasePool *pool = [[NSAutorelease alloc] init];
[pool autorelease];
會(huì)發(fā)生異常。
通過在使用OC,也就是foundation框架時(shí),無論調(diào)用哪一個(gè)對(duì)象的autorelease實(shí)例方法,實(shí)際上是調(diào)用的都是NSObject類的autorelease實(shí)例方法。但是對(duì)于NSAutoreleasePool類,autorelease實(shí)例方法已被該類重載,因此運(yùn)行時(shí)就會(huì)出錯(cuò)。
1.3 ARC規(guī)則
1.3.1 概要
上一節(jié)主要是講了OC的內(nèi)存管理,本節(jié)講述ARC所引起的變化。
實(shí)際上“引用計(jì)數(shù)式內(nèi)存管理”的本質(zhì)部分在ARC中并沒有改變。就像“自動(dòng)引用計(jì)數(shù)”這個(gè)名稱表示的那樣,ARC只是自動(dòng)的幫助我們處理“引用計(jì)數(shù)”的相關(guān)部分。
統(tǒng)一程序中按文件單位可以選擇ARC有效或無效。
設(shè)置ARC有效:指定編譯器屬性為“-fobjc-arc”(默認(rèn))
1.3.2 內(nèi)存管理的思考方式
引用計(jì)數(shù)式內(nèi)存管理的思考方式就是思考ARC所引起的變化。
- 自己生成的對(duì)象,自己持有;
- 非自己生成的對(duì)象,自己也能持有;
- 不再需要自己持有的對(duì)象時(shí),釋放;
- 非自己持有的對(duì)象,無法釋放;
這一思考方式在ARC有效時(shí)也是可行的。只是在源代碼的記述方法上稍有不同。到底有什么不同?首先要理解ARC中追加的所有權(quán)聲明。
1.3.3 所有權(quán)修飾符
OC為了處理對(duì)象,可將變量類型定義為id類型或各種對(duì)象類型。
所謂對(duì)象類型就是指向NSObject這樣的OC類的指針,例如“NSObject *”。id類型用于隱藏對(duì)象類型的類名部分,相當(dāng)于C語言中常用的“void *”。
ARC有效時(shí),id類型和對(duì)象類型同C語言其他類型不同,其類型上必須附加所有權(quán)修飾符,所有權(quán)修飾符一共有4種:
- __strong 修飾符
- __weak 修飾符
- __unsafe_unretained修飾符
- __autoreleasing修飾符
__strong 修飾符
__strong修飾符是id類型和對(duì)象類型默認(rèn)的所有權(quán)修飾符。也就是說,一下源代碼中的id變量,實(shí)際上被附加了所有權(quán)修飾符。
id obj = [[NSObject alloc] init];
id和對(duì)象類型在沒有明確指定所有權(quán)修飾符時(shí),默認(rèn)為__strong修飾符。上面的源代碼與以下相同。
id __strong obj = [[NSObject alloc] init];
ARC無效時(shí):
{
id obj = [[NSObject alloc] init];
[obj release];
}
為了釋放生成并持有的對(duì)象,增加了調(diào)用release方法的代碼。該源代碼進(jìn)行的動(dòng)作同先前ARC有效時(shí)的動(dòng)作完全一樣。
如此源代碼所示,附有__strong修飾符的變量obj在超出其變量作用域時(shí),既在該變量被廢棄時(shí),會(huì)釋放其被賦予的對(duì)象。
如“strong”這個(gè)名稱所示,__strong修飾符表示對(duì)對(duì)象的“強(qiáng)引用”。持有強(qiáng)引用的變量在超出其作用域時(shí)被廢棄,隨著強(qiáng)引用的實(shí)效,引用的對(duì)象會(huì)隨之釋放。
{
id obj = [[NSObject alloc] init]; //自己生成并持有對(duì)象:因?yàn)樽兞縪bj為強(qiáng)引用,所以自己持有對(duì)象。
}
因?yàn)樽兞縪bj超出其作用域,強(qiáng)引用失效,所以自動(dòng)的釋放自己持有的對(duì)象。對(duì)象的所有者不存在,因此廢棄該對(duì)象。
此外,對(duì)象所有者和對(duì)象的生存周期是明確的。那么,在取得非自己生成并持有的對(duì)象時(shí),又會(huì)如何呢?
在NSMutableArray類的array類方法的源代碼中取得非自己生成并持有的對(duì)象,具體如下:
{
id obj = [NSMutableArrray array]; //取得非自己生成但是自己持有的對(duì)象
}
因?yàn)樽兞縪bj超出其作用域,強(qiáng)引用失效,所以自動(dòng)的釋放自己持有的對(duì)象。對(duì)象的所有者不存在,因此廢棄該對(duì)象。
當(dāng)然,附有__strong修飾符的變量之間可以相互賦值。
// obj0持有對(duì)象A的強(qiáng)引用
id __strong obj0 = [[NSObject alloc] init]; // 對(duì)象A
// obj1持有對(duì)象B的強(qiáng)引用
id __strong obj1 = [[NSObject alloc] init]; // 對(duì)象B
// obj2不持有任何對(duì)象
id __strong obj2 = nil;
// obj0持有obj1賦值的對(duì)象B的強(qiáng)引用,因?yàn)閛bj0被賦值,所以原先持有的對(duì)對(duì)象A的強(qiáng)引用失效。對(duì)象A的所有者不存在,因此廢棄對(duì)象A。此時(shí),持有對(duì)象B的強(qiáng)引用的變量為obj0和obj1。
obj0 = obj1;
// obj2持有obj0賦值的對(duì)象B的強(qiáng)引用,持有對(duì)象B的強(qiáng)引用。此時(shí),持有對(duì)象B的強(qiáng)引用的變量為obj0、obj1和obj2。
obj2 = obj0;
// 因?yàn)閚il賦給了obj1,所以對(duì)對(duì)象B的強(qiáng)引用失效。此時(shí)持有對(duì)象B的強(qiáng)引用的變量為obj0和obj2。
obj1 = nil;
// 因?yàn)閚il賦給了obj0和obj2,所以對(duì)對(duì)象B的強(qiáng)引用失效。對(duì)象B的所有者不存在,因此廢棄對(duì)象B。
obj0 = nil;
obj2 = nil;
通過上面這些不難發(fā)現(xiàn),__strong修飾符的變量,不僅只在變量作用域中,在賦值上也能夠正確的管理其對(duì)象的所有者。
當(dāng)然,即便是OC類成員變量,也可以在方法參數(shù)上,使用附有__strong修飾符的變量。
__strong修飾符同后面要將的__weak修飾符和_autorelease修飾符一起,可以保證將附有這些修飾符的自動(dòng)變量初始化為nil。
@interface Test : NSobject
{
id __strong obj_;
}
- (void)setObject:(id __strong)obj;
@end
@implement
- (id)int {
self = [super init];
return self;
}
- (void)setObject:(id __strong)obj {
obj_ = obj;
}
@end
下面試著使用該類。
{
id __strong test = [[Test alloc] init];
[test setObject: [[NSObject alloc] init]];
}
該例中生成并持有對(duì)象的狀態(tài)記錄如下:
{
// test持有Test對(duì)象的強(qiáng)引用。
id __strong test = [[Test alloc] init];
//Test對(duì)象的obj_成員,持有NSObject對(duì)象的強(qiáng)引用。
[test setObject:[[NSObject allokc] init]];
}
因?yàn)閠est變量超出其作用域,強(qiáng)引用失效,所以自動(dòng)釋放Test對(duì)象。test對(duì)象所有者不存在,因此廢棄該對(duì)象。
廢棄Test對(duì)象的同時(shí),Test對(duì)象的obj_成本也被廢棄,NSObject對(duì)象的強(qiáng)引用失效,自動(dòng)釋放NSObject對(duì)象。NSobject對(duì)象的所有者不存在,因此廢棄該對(duì)象。
像這樣,無需額外工作便可以使用于類成員變量以及方法參數(shù)中。
另外,__strong修飾符同后面要講的__weak修飾符和__autoreleaseing 修飾符一起,可以保證將附有這些修飾符的自動(dòng)變量初始化為nil。
id __strong obj0;
id __weak obj1;
id __autoreleasing obj2;
以下源代碼與上相同。
id __strong obj0 = nil;
id __weak obj1 = nil;
id __autoreleasing obj2 = nil;
正如蘋果所言,通過__strong修飾符,不必再次鍵入retain或者release,完美的滿足了“引用計(jì)數(shù)式內(nèi)存管理的思考方式:
- 自己生成的對(duì)象,自己持有;
- 非自己生成的對(duì)象,自己也能持有;
- 不再需要自己持有的對(duì)象時(shí),釋放;
- 非自己持有的對(duì)象,無法釋放;
前兩項(xiàng)“自己生成的對(duì)象,自己持有”和“非自己生成的對(duì)象,自己也能持有”只需通過對(duì)帶__strong修飾符的變量賦值便可達(dá)成。通過廢棄帶__strong修飾符的變量(變量作用域結(jié)束或是成員變量所屬對(duì)象廢棄)或者對(duì)變量賦值,都可以做到“不再需要自己持有的對(duì)象時(shí)釋放”,最后一項(xiàng)“非自己持有的對(duì)象無法釋放”,由于不必再次鍵入release,所以原本就不會(huì)執(zhí)行。者些都滿足于引用計(jì)數(shù)式內(nèi)存管理的思考方式。
因?yàn)閕d類型和對(duì)象類型的所有權(quán)修飾符默認(rèn)為__strong修飾符,所以不需要寫上“__strong”。使ARC有效減淡的編程遵循了OC內(nèi)存管理的思考方式。
__weak修飾符
看起來好像通過__strong修飾符編譯器就能夠完美的進(jìn)行內(nèi)存管理。但是遺憾的是,僅通過__strong修飾符是不能解決有些重大問題的。
這里提到的重大問題就是引用計(jì)數(shù)式內(nèi)存管理中必然會(huì)發(fā)生的“循環(huán)引用”的問題。
以下為循環(huán)引用:
{
id test0 = [[Test alloc] init]; //對(duì)象A。test0持有Test對(duì)象A的強(qiáng)引用
id test1 = [[Test alloc] init]; //對(duì)象B。test1持有Test對(duì)象B的強(qiáng)引用
// Test對(duì)象A的obj_成員變量持有Test對(duì)象B的強(qiáng)引用。此時(shí),持有Test對(duì)象B的強(qiáng)引用的變量為Test對(duì)象A的obj_和test1.
[test0 setObject:test1];
// Test對(duì)象B的obj_成員變量持有Test對(duì)象A的強(qiáng)引用。此時(shí),持有Test對(duì)象A的強(qiáng)引用的變量為Test對(duì)象B的obj_和test0.
[test0 setObject:test1];
}
因?yàn)閠est0變量超出其作用域,強(qiáng)引用失效,所以自動(dòng)釋放Test對(duì)象A。
因?yàn)閠est1變量超出其作用域,強(qiáng)引用失效,所以自動(dòng)釋放Test對(duì)象B。
此時(shí),持有Test對(duì)象A的強(qiáng)引用的變量為Test對(duì)象B的obj_。
此時(shí),持有Test對(duì)象B的強(qiáng)引用的變量為Test對(duì)象A的obj_。
發(fā)生內(nèi)存泄漏!
循環(huán)引用容易發(fā)生內(nèi)存泄漏。所謂內(nèi)存泄漏就是應(yīng)當(dāng)廢棄的對(duì)象在超出其生存周期后繼續(xù)存在,
此代碼的本意是賦予變量test0的對(duì)象A和賦予變量test1的對(duì)象B在超出其變量作用域時(shí)被釋放,既在對(duì)象不被任何變量持有的狀態(tài)下予以廢棄。但是,循環(huán)引用使得對(duì)象不能被再次廢棄。
像下面這種情況,雖然只有一個(gè)對(duì)象,但在該對(duì)象持有其自身時(shí),也會(huì)發(fā)生循環(huán)引用(自引用)。
id test = [[Test alloc] init];
[test setObject:test];
怎么樣才能避免循環(huán)引用呢?看到__strong修飾符就會(huì)意識(shí)到了,既然有strong,就應(yīng)該有與之對(duì)象的weak,也就是說,使用__weak修飾符可以避免循環(huán)引用。
__weak修飾符與__strong修飾符相反,提供若引用。若引用不能持有對(duì)象實(shí)例。
id __weak obj = [[NSObject alloc] init];
變量obj上附加了__weak修飾符。實(shí)際上如果編譯以上代碼,編譯器會(huì)發(fā)出警告。
此源代碼將自己生成并持有的對(duì)象賦值給附有__weak修飾符的變量obj。既變量obj持有對(duì)持有對(duì)象的若引用。因此,為了不以自己持有的狀態(tài)來保存自己生成并持有的對(duì)象,生成的對(duì)象會(huì)立即被釋放。編譯器對(duì)此會(huì)給出警告。如果像下面這樣,將對(duì)象賦值給附有_strong修飾符的變量之后再賦值給附有__weak修飾符的變量,就不會(huì)發(fā)生警告了。
{
id __strong obj0 = [[NSObject alloc] init]; //自己生成并持有對(duì)象
id __weak obj1 = obj0; //obj1變量持有生成對(duì)象的弱引用
}
因?yàn)閛bj0編程超出其作用域,強(qiáng)引用失效,所以自動(dòng)釋放自己持有的對(duì)象。因?yàn)閷?duì)象的所有者不存在,所以廢棄該對(duì)象。
因?yàn)閹_weak修飾符的變量(既若引用)不持有對(duì)象,所以在超出其變量作用域時(shí),對(duì)象既被釋放。
__weak修飾符還有另一有點(diǎn)。在持有某對(duì)象的若引用時(shí),若該對(duì)象被廢棄,則此若引用將自動(dòng)失效且處于nil被賦值的狀態(tài)(空弱引用)。
__unsafe_unretained修飾符
__unsafe_unretained修飾符正如其名unsafe所示,是不安全的所有權(quán)修飾符。盡管ARC式的內(nèi)存管理是編譯器的工作,但附有__unsafe_unretained修飾符的變量不屬于編譯器的內(nèi)存管理對(duì)象。這一點(diǎn)在使用時(shí)要注意。
id __unsafe_unretained obj = [[NSObject alloc] init];
附有__unsafe_unretained修飾符的變量同附有__weak修飾符的變量一樣,因?yàn)樽约荷刹⒊钟械膶?duì)象不能繼續(xù)為自己所有,所以生成的對(duì)象立即被釋放。
在iOS4的程序中,必須使用__unsafe_unretained來替代__weak修飾符,賦值給附有__unsafe_unretained修飾符變量的對(duì)象子啊通過該變量使用時(shí),如果沒有確保其確實(shí)存在,那么應(yīng)用程序就會(huì)崩潰。
__autoreleasing修飾符
ARC有效時(shí)autorelease會(huì)如何呢?不能使用autorelease方法,另外,也不能使用NSAutoreleasePool類。這樣以來,雖然autorelease無法直接使用,但實(shí)際上,ARC有效時(shí)autorelease功能是起作用的。
ARC無效時(shí)會(huì)像下面這樣來使用
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelase];
[pool drain];
ARC有效時(shí),該源代碼也能寫成下面這樣
@autorelasepool {
id __autorelaseing obj = [[NSObject alloc] init];
}
指定“@autoreleasepool塊”來替代“NSAutoreleasePool類對(duì)象生成、持有即廢棄”這一范圍。
另外,ARC有效時(shí),要通過將對(duì)象賦值給附加了__autoreleasing修飾符的變量來替代調(diào)用autorelease方法。對(duì)象賦值給附有__autoreleasing修飾符的變量等價(jià)于在ARC無效時(shí)調(diào)用對(duì)象的autorelease方法,即對(duì)象被注冊(cè)到autoreleasepool。
也就是可以理解為,在ARC無效時(shí),用@autoreleasepool塊替代NSAutoreleasePool類,用附有__autoreleasing修飾符的變量替代autorelease方法。
但是,顯式的附加__autoreleasing修飾符同顯式的附加__strong修飾符一樣罕見。
我們通過實(shí)例來看看為什么顯式的使用__autoreleasing修飾符也可以。
取得非自己生成并持有的對(duì)象時(shí),如同以下源代碼,雖然可以使用alloc/new/copy/mutableCopy以外的方法來取得對(duì)象,但該對(duì)象已被注冊(cè)到了autoreleasepool。這同在ARC無效時(shí)取得已調(diào)用了autorelease方法的對(duì)象一樣的。這是由于編譯器會(huì)檢查方法名是否以alloc/new/copy/mutableCopy開始,如果不是則自動(dòng)將返回值的對(duì)象注冊(cè)到autoreleasepool。
另外根據(jù)后面要講到的遵守內(nèi)存管理方法命名規(guī)則(參考1.3.4),init方法返回值的對(duì)象不注冊(cè)到autoreleasepool。
我們?cè)賮砜纯丛撛创a中對(duì)象的所有情況:
@autoreleasepoop {
id __strong obj = [NSMutableArray array];//取得非自己生成并持有的對(duì)象。因?yàn)樽兞縪bj為強(qiáng)引用,所以自己持有對(duì)象。并且該對(duì)象由編譯器判斷其方法名后,自動(dòng)注冊(cè)到autoreleasepool。
}
因?yàn)樽兞縪bj超出其作用域,強(qiáng)引用失效,所以自動(dòng)釋放自己持有的對(duì)象。
同時(shí),隨著@autoreleasepoop塊的結(jié)束,注冊(cè)到autoreleasepool中的所有對(duì)象被自動(dòng)釋放。因?yàn)閷?duì)象的所有者不存在,所以廢棄對(duì)象。
像這樣,不使用__autoreleasing修飾符也能使對(duì)象注冊(cè)到auyoreleasepool,以下為取得非自己生成并持有對(duì)象時(shí)被調(diào)用方法的源代碼示例。
+ (id)array {
return [[NSMutableArray alloc] init];
}
該源代碼也沒有使用__autoreleasing修飾符,可寫成以下形式。
+ (id)array {
id obj = [[NSMutableArray alloc] init];
return obj;
}
因?yàn)闆]有顯式指定所有權(quán)修飾符,所以id obj同附有__strong修飾符的id __strong obj是完全一樣的。由于return使得對(duì)象變量超出其作用域,所以該強(qiáng)引用對(duì)應(yīng)的自己持有的對(duì)象會(huì)被自動(dòng)釋放,但該對(duì)象作為函數(shù)的返回值,編譯器會(huì)自動(dòng)將其注冊(cè)到autoreleasepool。
以下為使用__weak修飾符的例子。雖然__weak修飾符是為了避免循環(huán)引用而使用的,但在訪問附有__weak修飾符的變量時(shí),實(shí)際上必定要訪問注冊(cè)到autoreloeasepool的對(duì)象。
id __weak obj1 = obj0;
以下源代碼與此相同。
id __weak obj1 = obj0;
id __autoreleasing tmp = obj1;
為什么在訪問附有__weak修飾符的變量時(shí)必須訪問注冊(cè)到autoreleasepool的對(duì)象呢?這是因?yàn)開_weak修飾符只持有對(duì)象的弱引用,而在訪問引用對(duì)象的過程中,該對(duì)象有可能被廢棄。如果把要訪問的對(duì)象注冊(cè)到autoreleasepool中,那么在@autoreleasepool塊結(jié)束之前都能確保該對(duì)象存在。因此,在使用附有__weak修飾符的變量時(shí)就必定要使用注冊(cè)到autoreleasepool中的對(duì)象。
最后一個(gè)可非顯式的使用__autoreleasing修飾符的例子,同前面講述的id obj和id __strong obj完全一樣。那么id *obj又如何呢?可以由id __strong obj的例子類推出 id __strong *obj嗎?其實(shí),推出來的是id __autoreleasing *obj。同樣的,對(duì)象的指針NSObject **obj便稱為了NSObject *__autoreleasing *obj。
像這樣,id的指針或?qū)ο蟮闹羔樤跊]有顯式指定時(shí)會(huì)被附加上__autoreleasing修飾符。
比如,為了得到詳細(xì)的錯(cuò)誤信息,經(jīng)常會(huì)在方法的參數(shù)中傳遞NSError對(duì)象的指針,而不是函數(shù)返回值。使用該方法的源代碼如下所示。
NSEoor *error = nil;
BOOL result = [obj performOperationWithError:&error];
該方法的聲明為:
- (BOOL)performOperationWithError:(NSError **)error;
同前面講述的一樣,id的指針或?qū)ο蟮闹羔槙?huì)默認(rèn)附加上__autoreleasing修飾符,所以等同于以下源代碼。
- (BOOL)performOperationWithError:(NSError * __autoreleasing*)error;
NSRunLiio等實(shí)現(xiàn)不論ARC有效還是無效,均能夠隨時(shí)釋放注冊(cè)到autoreleasepool中的對(duì)象。
[待補(bǔ)充]
1.3.4 規(guī)則
在ARC有效的情況下編譯源代碼,必須遵守一定的規(guī)則。下面就是具體的ARC的規(guī)則
- 不能使用retain/release/retainCount/autorelease
- 不能使用NSAllocateObject/NSDeallocateObject
- 須遵守內(nèi)存管理的方法命名規(guī)則
- 不要顯式調(diào)用dealloc
- 使用@autoreleasepool塊替代NSAutoreleasePool
- 不能使用區(qū)域(NSZone)
- 對(duì)象型變量不能作為C語言結(jié)構(gòu)體(struct/union)的成員
- 顯式轉(zhuǎn)換“id”和“void *”。
下面詳細(xì)解釋各項(xiàng)
不能使用retain/release/retainCount/autorelease
內(nèi)存管理是編譯器的工作,因此沒有必要使用內(nèi)存管理的方法(retain/release/retainCount/autorelease)??傊?,只能在ARC無效且手動(dòng)進(jìn)行內(nèi)存管理時(shí)使用retain/release/retainCount/autorelease。
不能使用NSAllocateObject/NSDeallocateObject
alloc方法,實(shí)際上是通過直接調(diào)用NSAllocateObject函數(shù)來生成并持有對(duì)象的。在ARC有效時(shí),禁止使用NSAllocateObject/NSDeallocateObject函數(shù),同retain等方法一樣,如果使用便會(huì)引起編譯錯(cuò)誤。
須遵守內(nèi)存管理的方法命名規(guī)則
在ARC無效時(shí),用于對(duì)象生成/持有的方法必須遵守以下的命名規(guī)則。
- allock
- new
- copy
- mutableCopy
以上述名稱開始的方法在返回對(duì)象時(shí),必須返回給調(diào)用方所應(yīng)當(dāng)持有的對(duì)象。這在ARC有效時(shí)也一樣,返回的對(duì)象完全沒有改變。只是在ARC有效時(shí)要追加一條命名規(guī)則。
- init
以init開始的方法的規(guī)則要比alloc/new/copy/mutableCopy更嚴(yán)格。該方法必須是實(shí)例方法,并且必須要返回對(duì)象。該返回對(duì)象并不注冊(cè)到autoreleasepool上?;旧现皇菍?duì)alloc方法返回值的對(duì)象進(jìn)行初始化處理,然后原封不動(dòng)的返還給調(diào)用方。
不要顯式調(diào)用dealloc
無論ARC是否有效,只要對(duì)象的所有者都不持有該對(duì)象,該對(duì)象就被廢棄。對(duì)象被廢棄時(shí),不管ARC是否有效,都會(huì)調(diào)用對(duì)象的dealloc方法。
例如使用C語言庫,在該庫內(nèi)部分配緩存時(shí),dealloc方法需要通過free來釋放留出的內(nèi)存。
- (void)dealloc {
free(buffer_);
}
dealloc方法在大多數(shù)情況下還適用于刪除已注冊(cè)的代理或觀察者對(duì)象。
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
另外,在ARC無效時(shí)必須調(diào)用 [super dealloc];。
- (void)dealloc {
[super dealloc];
}
ARC有效時(shí)會(huì)遵循無法顯式調(diào)用dealloc這一規(guī)則,如果調(diào)用會(huì)引起編譯錯(cuò)誤。
使用@autoreleasepool塊替代NSAutoreleasePool
ARC有效時(shí),使用@autoreleasepool塊替代NSAutoreleasePool
不能使用區(qū)域(NSZone)
不管ARC是否有效,區(qū)域在現(xiàn)在的運(yùn)行時(shí)系統(tǒng)中已單純的被忽略。
對(duì)象型變量不能作為C語言結(jié)構(gòu)體(struct/union)的成員
C語言的規(guī)約上沒有方法來管理結(jié)構(gòu)體成員的生存周期。因?yàn)锳RC把內(nèi)存管理的工作分配給編譯器,所以編譯器必須能夠知道并管理對(duì)象的生存周期。例如C語言的自動(dòng)變量(局部變量)可使用該變量的作用域管理對(duì)象。但是對(duì)于C語言的結(jié)構(gòu)體成員來說,這在標(biāo)準(zhǔn)上就是不可實(shí)現(xiàn)的。
要把對(duì)象型變量加入到結(jié)構(gòu)體成員中時(shí),可強(qiáng)制轉(zhuǎn)換為void *(見下一條規(guī)則)或是附加前面所述的__unsafe_unretained修飾符。
struct Data {
NSMutableArray __unsafe_unretained *array;
}
如前所述,附有__unsafe_unretained修飾符的變量不屬于編譯器的內(nèi)存管理對(duì)象。如果管理師不注意賦值對(duì)象的所有者,便有可能遭遇內(nèi)存泄露或程序崩潰。這點(diǎn)在使用時(shí)應(yīng)多加注意。
顯式轉(zhuǎn)換“id”和“void *”。
id型或?qū)ο箢愋唾x值給void *或者逆向賦值時(shí)都需要進(jìn)行特定的轉(zhuǎn)換。如果只想單純的賦值,則可以使用“__bridge轉(zhuǎn)換”。
id obj = [[NSObject alloc] init];
void *p = (__bridge void *)obj;
id o = (__bridge id)p;
像這樣,通過“__bridge轉(zhuǎn)換”,id和void *就能夠相互轉(zhuǎn)換。
但是轉(zhuǎn)換為void *的__bridge轉(zhuǎn)換,其安全性與賦值給__unsafe_unretained修飾符相近,甚至?xí)?。如果管理時(shí)不注意賦值對(duì)象的所有者,就會(huì)因懸垂指針而導(dǎo)致程序崩潰。
__bridge轉(zhuǎn)換中還有另外兩種轉(zhuǎn)換,分別是“__bridge_retained轉(zhuǎn)換”和“__bridge_transfer”轉(zhuǎn)換。
這些轉(zhuǎn)換多數(shù)使用在OC對(duì)象與Core Foundation對(duì)象之間的相互變換中。
OC對(duì)象與Core Foundation對(duì)象
Core Foundation對(duì)象主要使用在用C語言編寫的Core Foundation框架中,并使用引用計(jì)數(shù)的對(duì)象。在ARC無效時(shí),Core Foundation框架中的retain/release分別是CFRetain/CFRelease。
CF對(duì)象與OC對(duì)象的區(qū)別很小,不同之處在于是由哪一個(gè)框架(Foundation框架還是CF框架)所生成的。無論是由哪種框架生成的對(duì)象,一旦生成之后,便能在不同的框架中使用。Foundation框架的API生成并持有的對(duì)象可以用CF框架的API釋放。當(dāng)然,反過來也是可以的。
因?yàn)镃F對(duì)象與OC對(duì)象沒有區(qū)別,所以在ARC無效時(shí),只用簡單的C語言的轉(zhuǎn)換也能實(shí)現(xiàn)互換。另外這種轉(zhuǎn)換不需要使用額外的CPU資源,因此也被稱為“免費(fèi)橋”(Toll-Free Bridge)。
[待補(bǔ)充]
1.3.5 屬性
當(dāng)ARC有效時(shí),OC類的屬性也會(huì)發(fā)生變化
@property (nonatomic,strong) NSString *name;
當(dāng)ARC有效時(shí),以下可作為這種屬性聲明中使用的屬性來用。
| 屬性聲明的屬性 | 所有權(quán)修飾符 |
|---|---|
| assign | __unsafe_unretained修飾符 |
| copy | __strong修飾符(但是賦值的是被復(fù)制的對(duì)象) |
| retain | __strong修飾符 |
| strong | __strong修飾符 |
| __unsafe_unretained | __unsafe_unretained修飾符 |
| weak | __weak修飾符 |
以上各種屬性賦值給指定的屬性中就相當(dāng)于賦值給附加各屬性對(duì)應(yīng)的所有權(quán)修飾符的變量中。只有copy屬性不是簡單的賦值,它賦值的是通過NSCopying接口的copyWithZone:方法復(fù)制賦值源所生成的對(duì)象。
另外,在聲明類成員變量時(shí),如果同屬性聲明中的屬性不一致則會(huì)引起編譯錯(cuò)誤。比如下面這種情況:
@interface TestObject()
{
id obj;
}
@property (nonatomic,weak) id obj;
@end
此時(shí),成員變量的聲明中需要附加__weak修飾符。
@interface TestObject()
{
id __weak obj;
}
@property (nonatomic,weak) id obj;
@end
或者使用strong屬性替代weak屬性
@property (nonatomic,strong) id obj;
1.3.6 數(shù)組
[待補(bǔ)充]
以下是將附有__strong修飾符的變量作為靜態(tài)數(shù)組使用的情況。
id objs[10];
__weak 修飾符,__autoreleasing修飾符以及__unsafe_unretained修飾符也與此相同。
id __weak objs[10];
__unsafe_unretained修飾符以外的__strong/__weak_autoreleasing修飾符保證其指定的變量初始化為nil。同樣的,附有__strong/__weak_autoreleasing修飾符變量的數(shù)組也保證其初始化為nil。下面我們就來看看數(shù)組中使用附有__strong修飾符變量的例子。
{
id objs[2];
objs[0] = [[NSObject alloc] init];
objs[1] = [NSMutableArray array];
}
數(shù)組超出其變量作用域時(shí),數(shù)組中各個(gè)附有__strong修飾符的變量也隨之失效,其強(qiáng)引用消失,所賦值的對(duì)象也隨之釋放。這與不使用數(shù)組的情形完全一樣。
將附有__strong修飾符的變量作為動(dòng)態(tài)數(shù)組來使用時(shí)又如何呢?在這種情況下,根據(jù)不同的目的選擇使用NSMutableArray、NSMutableDictionary、NSMutableSet等Foundation框架的容器。這些容器會(huì)恰當(dāng)?shù)某钟凶芳拥膶?duì)象并為我們管理這些對(duì)象。
1.4 ARC的實(shí)現(xiàn)
蘋果的官方說明中稱,ARC是“由編譯器進(jìn)行內(nèi)存管理”的,但實(shí)際上只有編譯器是無法完全勝任的,在此基礎(chǔ)上還需要OC運(yùn)行時(shí)庫的協(xié)助。ARC由以下工具、庫來實(shí)現(xiàn)。
- clang(LLVM編譯器)3.0以上;
- obj4 OC運(yùn)行時(shí)庫493.9以上;
如果按照蘋果的官方說明,假設(shè)僅由編譯器進(jìn)行ARC式的內(nèi)存管理,那么__weak修飾符也完全可以使用在iOS4中。在iOS4中,無論怎樣靜態(tài)鏈接用于ARC的庫,也不能在對(duì)象廢棄時(shí)將__weak變量初始化為nil(空弱引用)。
下文基于“實(shí)現(xiàn)”來研究一下ARC。
1.4.1 __strong修飾符
[待補(bǔ)充]
賦值給附有__strong修飾符的變量在實(shí)際的程序中到底是怎樣運(yùn)行的呢?
{
id __strong obj = [[NSObject alloc] init];
}
該源代碼實(shí)際上可轉(zhuǎn)換為調(diào)用以下的函數(shù)。
/*編譯器的模擬代碼 */
id obj = pbjc_msgSend(NSObject,@selector(alloc));
objc_msgSend(obj,@selector(init));
objc_release(obj);
如源代碼所示,2次調(diào)用objc_msgSend方法(alloc方法和init方法),變量作用域結(jié)束時(shí)通過objc_release釋放對(duì)象。雖然ARC有效時(shí)不能使用release方法,但由此可知編譯器自動(dòng)插入了release。下面我們來看看使用alloc/new/copy/mutableCopy以外的方法會(huì)是什么情況。
{
id __strong obj = [NSMutableArray array];
}
雖然調(diào)用了我們熟知的NSMutableArray類的array類方法,但得到的結(jié)果卻與之前稍有不同。
/*編譯器的模擬代碼 */
id obj = objc_msgSend(NSMutableArray,@selector(array));
objc_retainAutoreleaseReturnValue(obj);
objc_release(obj);
雖然最開始的array方法的調(diào)用以及最后變量作用域結(jié)束時(shí)的release與之前相同,但中間的objc_retainAutoreleasedReturnValue函數(shù)是什么呢?
objc_retainAutoreleasedReturnValue 函數(shù)主要用于最優(yōu)化程序運(yùn)行。顧名思義,它是用于自己持有(retain)對(duì)象的函數(shù),但它持有的對(duì)象應(yīng)為返回注冊(cè)在autoreleasepool中對(duì)象的方法,或是函數(shù)的返回值。像該源代碼這樣,在調(diào)用alloc/new/copy/mutableCopy以外的方法,即NSMutableArray類的array類方法等調(diào)用之后,由編譯器插入該函數(shù)。
這種objc_retainAutoreleasedReturnvalue函數(shù)是成對(duì)的,與之相對(duì)的函數(shù)是objc_autoreleaseReturnValue。它用于alloc/new/copy/mutableCopy方法以外的NSMutableArray類的array類方法等返回對(duì)象的實(shí)現(xiàn)上。下面我們看看NSMutableArray類通過編譯器會(huì)進(jìn)行怎樣的轉(zhuǎn)換。
+ (id)array {
return [[NSMutableArray alloc] init];
}
以下為該源代碼的轉(zhuǎn)換,轉(zhuǎn)換后的源代碼使用了objc_autoreleaseReturnValue函數(shù)。
/* 編譯器的模擬代碼*/
+ (id)array {
id obj = objc_msgSend(NSMutableArray,@selector(alloc));
objc_msgSend(obj,@selector(init));
return objc_autoreleaseReturnValue(obj);
}
像該源代碼這樣,返回注冊(cè)到autoreleasepool中對(duì)象的方法使用了objc_autoreleaseReturnValue函數(shù)返回注冊(cè)到autoreleasepool中的對(duì)象。但是objc_autoreleaseReturnValue函數(shù)同objc_autorelease函數(shù)不同,一般不僅限于注冊(cè)對(duì)象到autoreleasepool中。
objc_autoreleaseReturnValue函數(shù)會(huì)檢查使用該函數(shù)的方法或函數(shù)調(diào)用方的執(zhí)行命令列表,如果方法或函數(shù)的調(diào)用方在調(diào)用了方法或函數(shù)后緊接著調(diào)用objc_retainAutoreleasedReturnvalue()函數(shù),那么就不將返回的對(duì)象注冊(cè)到autoreleasepool中,而是直接傳遞到方法或函數(shù)的調(diào)用方。objc_retainAutoreleasedReturnValue函數(shù)與objc_retain函數(shù)不同,它即便不注冊(cè)到autoreleasepool中而返回對(duì)象,也能夠正確的獲取對(duì)象。通過objc_autoreleaseReturnValue函數(shù)和objc_retainAutoreleasedReturnValue函數(shù)的協(xié)作,可以不將對(duì)象注冊(cè)到autoreleasepool中而直接傳遞,這一過程達(dá)到了最優(yōu)化。

1.4.2 __weak修飾符
[待補(bǔ)充]
__weak修飾符提供的功能:
- 若附有__weak修飾符的變量所引用的對(duì)象被廢棄,則將nil賦值給該變量。
- 使用附有__weak修飾符的變量,即是使用注冊(cè)到autoreleasepool中的對(duì)象。
weak表與引用計(jì)數(shù)表(參考1.2.4節(jié))相同,作為散列表被實(shí)現(xiàn)。如果使用weak表,將廢棄對(duì)象的地址作為鍵值進(jìn)行檢索,就能告訴的獲取對(duì)應(yīng)的附有__weak修飾符的變量的地址。另外,由于一個(gè)對(duì)象可同時(shí)賦值給多個(gè)附有__weak修飾符的變量中,所以對(duì)于一個(gè)鍵值,可注冊(cè)多個(gè)變量的地址。
1.4.3 __autoreleasing修飾符
將對(duì)象賦值給附有__autoreleasing修飾符的變量等同于ARC無效時(shí)調(diào)用對(duì)象的autorelease方法。我們通過以下源代碼來看一下。
@autoreleasepool {
id __autoreleasing obj = [[NSObject alloc] init];
}
該源代碼主要將NSObject類對(duì)象注冊(cè)到autoreleasepoop中,可作如下變換:
/*編譯器的模擬代碼*/
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSObject,@selector(alloc));
objc_msgSend(obj,@selector(init));
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);
這與蘋果的autorelease實(shí)現(xiàn)中的說明(參考1.2.7節(jié))完全相同。雖然與ARC有效和無效時(shí),其在源代碼上的表現(xiàn)有所不同,但autorelease的功能完全一樣。
在alloc/new/copy/mutableCopy方法群之外的方法中使用被注冊(cè)到autoreleasepool中的對(duì)象會(huì)如何呢?下面我們來看看NSMutableArray類的array類方法。
@autoreleasepool {
id __autoreleasing obj = [NSMutableArray array];
}
這與前面的代碼源代碼有何不同呢?
/*編譯器的模擬代碼*/
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSMutableArray,@selector(array));
objc_retainAutoreleaseReturnvalue(obj);
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);
雖然持有對(duì)象的方法從alloc方法變?yōu)閛bjc_retainAutoreleaseReturnvalue函數(shù),但注冊(cè)autoreleasepool的方法沒有改變,仍是objc_autorelease函數(shù)。
1.4.4 引用計(jì)數(shù)
實(shí)際上,前面只講了引用計(jì)數(shù)式內(nèi)存管理的思維方式,特地沒有介紹引用計(jì)數(shù)值本身,因此在這里提供了獲取引用計(jì)數(shù)值的函數(shù)
uintptr_t _objc_rootRetainCount(id obj);
{
id __strong obj = [[NSObject alloc] init];
NSLog(@"retain count = %d", _objc_rootRetainCount(obj));
}
該源代碼中,對(duì)象僅通過變量obj的強(qiáng)引用被持有,所以為1。
retain count = 1
下面使用__weak修飾符。
{
id __strong obj = [[NSObject alloc] init];
id __weak o = obj;
NSLog(@"retain count = %d", _objc_rootRetainCount(obj));
}
由于弱引用并不持有對(duì)象,所以賦值給附有__weak修飾符的變量中也必定不會(huì)改變引用計(jì)數(shù)數(shù)值。
retain count = 1
結(jié)果同預(yù)想一樣,那么通過__autoreleasing修飾符向autoreleasepool注冊(cè)又會(huì)如何呢?
@autoreleasepool {
id __strong obj = [[NSObject alloc] init];
id __autoreleasing o = obj;
NSLog(@"retain count = %d",__objc_rootRetainCount(obj));
}
結(jié)果如下
retain count = 2;
對(duì)象被賦予__strong修飾符變量的強(qiáng)引用所持有,且被注冊(cè)到autoreleasepool中,所以為2。
以下確認(rèn)@autoreleasepool塊結(jié)束時(shí)釋放已注冊(cè)的對(duì)象。
id __strong obj = [[NSObject alloc] init];
@autoreleasepool {
id __autoreleasing o = obj;
NSLog(@"retain count = %d",__objc_rootRetainCount(obj));
}
NSLog(@"retain count = %d",__objc_rootRetainCount(obj));
在@ autoreleasepool塊之后也顯示引用計(jì)數(shù)數(shù)值。
retain count = 2; in @autoreleasepool
retain count = 1;
如我們預(yù)期的一樣,對(duì)象被釋放。
以下在通過附有__weak修飾符的變量使用對(duì)象時(shí),基于顯示autoreleasepool狀態(tài)的_objc_autoreleasePoolPrint函數(shù)來觀察注冊(cè)到autoreleasepool中的引用對(duì)象。
@autoreleasepool {
id __strong obj = [[NSObject alloc] init];
_objc_autoreleasePoolPrint();
id __weak o = obj;
NSLog(@"before using __weak: retain count = %d",__objc_rootRetainCount(obj));
NSLog(@"class = %@",[o class]);
NSLog(@"after using __weak: retain count = %d",__objc_rootRetainCount(obj));
_objc_autoreleasePoolPrint();
}
結(jié)果如下:
before using __weak: retain count = 1
class = NSObject
after using __weak: retain count = 2
通過以上過程我們可以看出,不使用__autoreleasing修飾符,僅使用附有__weak聲明的變量也能將引用對(duì)象注冊(cè)到了autoreleasepool中。
雖然以上這些例子均使用了_objc_rootRetainCount函數(shù),但實(shí)際上并不能夠完全信任該函數(shù)取得的數(shù)值。對(duì)于已釋放的對(duì)象以及不正確的對(duì)象地址,有時(shí)也返回“1”。另外,在多線程中使用對(duì)象的引用計(jì)數(shù)數(shù)值,因?yàn)榇嬗懈?jìng)態(tài)條件的問題,所以取得的數(shù)值不一定完全可信。
雖然在調(diào)試中_objc_rootRetainCount函數(shù)很有用,但最好在來哦家其所具有的問題的基礎(chǔ)上來使用。