iOS開發(fā)讀書筆記:Objective-C高級(jí)編程 iOS與OS X多線程和內(nèi)存管理-上篇(自動(dòng)引用計(jì)數(shù))

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)化。

省略autoreleasepool注冊(cè).png

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ǔ)上來使用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容