《Objective-C高級編程》自動引用計數(shù)

《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》

1.1 自動引用計數(shù)(ARC,Automatic Reference Counting)

自動引用計數(shù)是指內(nèi)存管理中對引用采取自動計數(shù)的技術(shù)眠冈。


“ 在LLVM編譯器中設置ARC為有效狀態(tài)囊陡,就無需再次鍵入retain或者是release代碼衅谷。”

1.2 內(nèi)存管理/引用計數(shù)

1.2.1 計數(shù)的內(nèi)存管理

<1> 對象操作與Objective-C方法的對應

圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》

<2> 對象操作所對應的Objective-C的方法和引用計數(shù)的變化如下:

圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》

<3> 有關Objective-C內(nèi)存管理的方法并不包含在Objective-C語言中蹋宦,而是在包含在Cocoa框架中。如下:

Cocoa框架、Foundation框架和NSObject類的關系

1.2.2 內(nèi)存管理的思考方式

內(nèi)存管理的思考方式有以下四種:

  • 自己生成的對象久信,自己所持有
  • 非自己生成的對象,自己也能持有
  • 不再需要自己持有的對象時釋放
  • 非自己持有的對象無法釋放
自己生成的對象漓摩,自己所持有

使用以下名稱開頭的方法意味著自己生成的對象只有自己持有:

  • alloc
  • new
  • copy
  • mutableCopy

??:

// alloc方法
id obj = [[NSObject alloc] init];//持有新生成的對象
//指向生成并持有[[NSObject alloc] init]的指針被賦給了obj裙士,也就是說obj這個指針強引用[[NSObject alloc] init]這個對象。

//new方法
id obj = [NSObject new];//持有新生成的對象

注意1:
這種將持有對象的指針賦給指針變量的情況不只局限于上面這四種方法名稱管毙,還包括以他們開頭的所有方法名稱:

  • allocThisObject
  • newThatObject
  • copyThisObject
  • mutableCopyThatObject

注意2:
下列幾個方法腿椎,并不屬于同一類別的方法:

  • allocate
  • newer
  • copying
  • mutableCopyed
非自己生成的對象,自己也能持有

用alloc/new/copy/mutableCoy以外的方法取得對象夭咬,因為非自己生成并持有啃炸,所以自己不是該對象的持有者。
但是通過retain方法卓舵,非自己生成的對象跟用alloc/new/copy/mutableCoy方法生成并持有的對象一樣南用,成為了自己所持有的

??:

id obj = [NSMutableArray array];//非自己生成并持有的對象
[obj retain];//持有新生成的對象

//注意: 這里[NSMutableArray array]返回的非自己持有的對象正是通過autorelease方法實現(xiàn)的。所以如果想持有這個對象掏湾,需要執(zhí)行retain方法才可以

不再需要自己持有的對象時釋放
  • 自己持有的對象裹虫,一旦不再需要,持有者有義務釋放該對象融击,務必使用release方法釋放筑公。
注意,是有義務尊浪,而不是有權(quán)利匣屡,注意兩個詞的不同

??:

id obj = [[NSObject alloc] init];//持有新生成的對象
[obj doSomething];//使用該對象做一些事情
[obj release];//事情做完了,釋放該對象
id obj = [NSMutableArray array];//非自己生成并持有的對象
[obj retain];//持有新生成的對象
[obj soSomething];//使用該對象做一些事情
[obj release];//事情做完了拇涤,釋放該對象
  • 使用autorelease方法捣作,可以使取得的對象存在,但自己不持有對象

  • 用來取得誰都不持有的對象的方法名不能以alloc/new/copy/mutableCopy開頭

  • 通過retain方法也能將調(diào)用autorelease方法取得的對象變?yōu)樽约撼钟?/p>

注意: autorelease提供了這樣一個功能:在對象超出其指定的生存范圍時能夠自動并正確地釋放


圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》
非自己持有的對象無法釋放

在釋放對象的時候工育,我們只能釋放已經(jīng)持有的對象虾宇,非自己持有的對象是不能被自己釋放的。

兩種不允許的情況:
1. 釋放一個已經(jīng)廢棄了的對象
id obj = [[NSObject alloc] init];//持有新生成的對象
[obj doSomething];//使用該對象
[obj release];//釋放該對象如绸,不再持有了
[obj release];//釋放已經(jīng)廢棄了的對象嘱朽,崩潰
2. 釋放自己不持有的對象
id obj = [NSMutableArray array];//非自己生成并持有的對象
[obj release];//釋放了非自己持有的對象

思考:哪些情況會使對象失去擁有者呢旭贬?

  1. 將指向某對象的指針變量指向另一個對象。
  2. 將指向某對象的指針變量設置為nil搪泳。
  3. 當程序釋放對象的某個擁有者時稀轨。
  4. 從collection類中刪除對象時。

1.2.3 alloc/retain/release/dealloc實現(xiàn)

借助開源軟件GNUstep的源代碼中alloc/retain/release/dealloc的實現(xiàn)來理解蘋果的Cocoa實現(xiàn)岸军》芄簦總結(jié)如下:

- 在Objective-C的對象中存在引用計數(shù)這一整數(shù)值
- 調(diào)用alloc或是retain方法后,引用計數(shù)值加1
- 調(diào)用release方法后艰赞,引用計數(shù)值減1
- 引用計數(shù)值為0時佣谐,調(diào)用dealloc方法廢棄對象
蘋果的實現(xiàn):

由于NSObject類的源代碼沒有公開,利用Xcode的調(diào)試器(lldb)和iOS大概追溯內(nèi)存管理和引用計數(shù)的實現(xiàn)方妖。通過追溯可以發(fā)現(xiàn)似乎和散列表(Hash)有關狭魂,這說明蘋果對引用計數(shù)的管理應該是通過散列表來執(zhí)行的。


圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》

在這張表里党觅,key為內(nèi)存塊地址雌澄,而對應的值為引用計數(shù)。也就是說杯瞻,它保存了這樣的信息:一些被引用的內(nèi)存塊各自對應的引用計數(shù)镐牺。

那么使用散列表來管理內(nèi)存有什么好處呢?

因為計數(shù)表保存內(nèi)存塊地址魁莉,我們就可以通過這張表來:

  • 確認損壞內(nèi)存塊的位置睬涧。


    圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》
  • 在檢測內(nèi)存泄漏時,可以查看各對象的持有者是否存在沛厨。

1.2.4 autorelease

autorelease 介紹

當對象超出其作用域時宙地,對象實例的release方法就會被調(diào)用,autorelease的具體使用方法如下:

  • 生成并持有NSAutoreleasePool對象逆皮。
  • 調(diào)用已分配對象的autorelease方法。
  • 廢棄NSAutoreleasePool對象参袱。


    圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》

所有調(diào)用過autorelease方法的對象电谣,在廢棄NSAutoreleasePool對象時,都將調(diào)用release方法(引用計數(shù)-1):

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];//相當于obj調(diào)用release方法

NSRunLoop在每次循環(huán)過程中抹蚀,NSAutoreleasePool對象都會被生成或廢棄剿牺。

如果有大量的autorelease變量,在NSAutoreleasePool對象廢棄之前(一旦監(jiān)聽到RunLoop即將進入睡眠等待狀態(tài)环壤,就釋放NSAutoreleasePool)晒来,都不會被銷毀,容易導致內(nèi)存激增的問題:

for (int i = 0; i < imageArray.count; i++)
{
    UIImage *image = imageArray[i];
    [image doSomething];
}
圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》

在這類情況下郑现,有必要在適當?shù)牡胤缴膳缺馈⒊钟谢驈U棄NSAutoreleasePool對象荧降。

for (int i = 0; i < imageArray.count; i++)
{
    //臨時pool
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    UIImage *image = imageArray[i];
    [image doSomething];
    [pool drain];
}
圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》

思考:什么時候會創(chuàng)建自動釋放池?
答:運行循環(huán)檢測到事件并啟動后攒读,就會創(chuàng)建自動釋放池朵诫,而且子線程的 runloop 默認是不工作的,無法主動創(chuàng)建薄扁,必須手動創(chuàng)建剪返。
??:
自定義的 NSOperation 類中的 main 方法里就必須添加自動釋放池。否則在出了作用域以后邓梅,自動釋放對象會因為沒有自動釋放池去處理自己而造成內(nèi)存泄露脱盲。

autorelease實現(xiàn)

同樣借助開源軟件GNUstep的源代碼中autorelease的實現(xiàn)來理解蘋果的autorelease實現(xiàn)∪沼В總結(jié)如下:

- autorelease實例方法的本質(zhì)就是調(diào)用NSAutoreleasePool對象的addObject類方法

- addObject類方法調(diào)用正在使用的NSAutoreleasePool對象的addObject實例方法
如果嵌套生成或持有的NSAutoreleasePool對象钱反,理所當然會使用最內(nèi)側(cè)的對象

- 如果調(diào)用NSObject類的autorelease實例方法,該對象將被追加到正在使用的NSAutoreleasePool對象中的數(shù)組中

- drain實例方法在廢棄autorelease對象數(shù)組之前殿遂,會先對數(shù)組中的所有對象調(diào)用release實例方法

蘋果的實現(xiàn):

autoreleasepool以一個隊列數(shù)組的形式實現(xiàn),主要通過下列三個函數(shù)完成.
? objc_autoreleasepoolPush(壓入)
? objc_autoreleasepoolPop(彈出)
? objc_autorelease(釋放內(nèi)部)

1.3 ARC規(guī)則

1.3.1 內(nèi)存管理的思考方式

ARC和非ARC機制下的內(nèi)存管理思想是一致的:

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

在ARC機制下,編譯器就可以自動進行內(nèi)存管理喷楣,減少了開發(fā)的工作量静檬。

1.3.2 所有權(quán)修飾符

雖然在ARC機制下,編譯器就可以自動進行內(nèi)存管理次舌。但我們有時仍需要四種所有權(quán)修飾符來配合ARC來進行內(nèi)存管理。如下:

  • __strong修飾符
  • __weak修飾符
  • __unsafe_unretained修飾符
  • __autoreleasing修飾符
對象類型: 指向NSObject這樣的Objective-C類的指針,如“NSObject ”疙咸。 
id類型: 用于隱藏對象類型的類名部分,相當于C語言中的“void ”风科。

__strong修飾符

  • __strong修飾符表示對對象的”強引用“撒轮。持有強引用的變量在超出其作用域時被廢棄,隨著強引用的失效贼穆,引用的對象會隨之釋放题山。

在__strong修飾符修飾的變量之間相互賦值的情況:

id __strong obj0 = [[NSObject alloc] init];//obj0 持有對象A
id __strong obj1 = [[NSObject alloc] init];//obj1 持有對象B
id __strong obj2 = nil;//ojb2不持有任何對象
obj0 = obj1;//obj0強引用對象B;而對象A不再被ojb0引用故痊,被廢棄
obj2 = obj0;//obj2強引用對象B(現(xiàn)在obj0顶瞳,ojb1,obj2都強引用對象B)
obj1 = nil;//obj1不再強引用對象B
obj0 = nil;//obj0不再強引用對象B
obj2 = nil;//obj2不再強引用對象B,不再有任何強引用引用對象B慨菱,對象B被廢棄
  • 附有__strong修飾符的變量之間可以相互賦值焰络。通過相互賦值,可以使得變量對對象的強引用失效抡柿,從而釋放原先持有的對象舔琅,轉(zhuǎn)而持有由另外一個變量賦值的新的對象的強引用。
  • 即時是Objective-C類成員變量洲劣,也可以在方法參數(shù)上备蚓,使用附有__strong修飾符的變量
  • __strong修飾符可以確保將附有__strong修飾符的自動變量(局部變量)初始化為nil(該規(guī)則適用于__weak修飾符和__autoreleasing修飾符)
<1> 通過__strong修飾符使ARC有效遵循了Objective-C內(nèi)存管理的思考方式
  • “自己生成的對象,自己持有”和“非自己生成的對象囱稽,自己也能持有”只需通過對帶__strong修飾符的變量賦值便可達成
  • 通過廢棄帶__strong修飾符的變量(變量作用域結(jié)束或是成員變量所屬對象廢棄)或者對變量賦值郊尝,都可以做到”不再需要自己持有的對象時釋放“
  • 由于不必再次鍵入release,所以”非自己持有的對象無法釋放“原本就不會執(zhí)行 战惊。 --- (在ARC有效時不能使用release方法的緣故是編譯器會自動插入release)
<2> __strong修飾符 是id類型和對象類型默認的所有權(quán)修飾符:
id obj = [NSObject alloc] init];

等同于

id __strong obj = [NSObject alloc] init];

其內(nèi)存管理的過程:

{
    id __strong obj = [NSObject alloc] init];//obj持有對象
}
//obj超出其作用域流昏,強引用失效

__strong修飾符表示對對象的強引用。持有強引用的變量在超出其作用域時被廢棄吞获。

<3> __strong內(nèi)部實現(xiàn):

生成并持有對象:

{
    id __strong obj = [NSObject alloc] init];//obj持有對象
}

編譯器的模擬代碼:

id obj = objc_mesgSend(NSObject, @selector(alloc));
objc_msgSend(obj,@selector(init));
objc_release(obj);//超出作用域况凉,釋放對象

使用命名規(guī)則以外的構(gòu)造方法 ,如NSMutableArray類的array類方法:

{
    id __strong obj = [NSMutableArray array];
}

編譯器的模擬代碼:

/* 編譯器的模擬代碼  */
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);
objc_retainAutoreleasedReturnValue的作用:持有對象,將對象注冊到autoreleasepool并返回各拷。

其中刁绒,NSMutableArray類的array類方法:

+ (id) array
{
    return [[NSMutableArray alloc] init];
}

編譯器的模擬代碼:

/* 編譯器的模擬代碼  */
+ (id) array 
{
    id obj = objc_msgSend(NSMutableArray, @selector(alloc));
    objc_msgSend(obj, @selector(init));
    return objc_autoreleaseReturnValue(obj);
}
objc_autoreleaseReturnValue:返回注冊到autoreleasepool的對象。
<4>__strong內(nèi)部實現(xiàn)的總結(jié)

如上所示烤黍, objc_retainAutoreleasedReturnValue函數(shù)和objc_autoreleaseReturnValue函數(shù)的協(xié)作知市,可以不將對象注冊到autoreleasePool中而直接傳遞,這一過程達到了最優(yōu)化速蕊。

圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》
  • objc_autoreleaseReturnValue函數(shù)用于alloc/new/copy/mutableCopy方法以外的NSMutableArray類的array類方法等返回對象的實現(xiàn)上嫂丙。

  • objc_autoreleaseReturnValue函數(shù)與objc_autorelease函數(shù)不同,一般不僅限于注冊對象到autoreleasePool中规哲。

  • objc_retainAutoreleasedReturnValue函數(shù)主要用于最優(yōu)化程序運行跟啤。即,它是用于自己持有(retain)對象的函數(shù)唉锌,但它持有的對象應為返回注冊在autoreleasePool中對象的方法或是函數(shù)的返回值腥光。

  • objc_retainAutoreleasedReturnValue函數(shù)與objc_retain函數(shù)不同,它即時不注冊到autoreleasePool而返回對象糊秆,也能夠正確地獲取對象。

  • objc_autoreleaseReturnValue函數(shù)會檢查使用該函數(shù)的方法或函數(shù)調(diào)用方的執(zhí)行命令列表议双,如果方法或函數(shù)的調(diào)用方在調(diào)用了方法或函數(shù)后緊跟著調(diào)用objc_retainAutoreleasedReturnValue函數(shù)痘番,那么就不將返回的對象注冊到autoreleasePool中,而是直接傳遞到方法或函數(shù)的調(diào)用方。

__weak修飾符

<1>__weak修飾符提供弱引用汞舱。弱引用不能持有對象伍纫。
  • 在持有“對象”的弱引用時,若該對象被廢棄昂芜,則此弱引用將自動失效且處于nil被賦值的狀態(tài)(空弱引用)
id __weak obj1 = nil;

{
    id __strong obj0 = [[NSObject alloc] init];
    obj1 = obj0;  // obj1變量持有NSObject對象的弱引用
    NSLog(@“A : %@”, obj1);  
    // output A : <NSObject: 0x731e180>
}
NSLog(@“B : %@”, obj1; // output B : (null)

  • 通過檢查附有__weak修飾符的變量是否為nil莹规,可以判斷被賦值的對象是否已廢棄
<2> __weak修飾符大多解決的是循環(huán)引用的問題。

??:

@interface Test:NSObject
{
    id __strong obj_;
}

- (void)setObject:(id __strong)obj;
@end

@implementation Test
- (id)init
{
    self = [super init];
    return self;
}

- (void)setObject:(id __strong)obj
{
    obj_ = obj;
}
@end

其引用的關系如下:

{
    id test0 = [[Test alloc] init];//test0強引用對象A
    id test1 = [[Test alloc] init];//test1強引用對象B
    [test0 setObject:test1];//test0強引用對象B
    [test1 setObject:test0];//test1強引用對象A
}

因為生成對象(第一泌神,第二行)和set方法(第三良漱,第四行)都是強引用,所以會造成兩個對象互相強引用對方的情況:


圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》

所以欢际,我們需要打破其中一種強引用:

@interface Test:NSObject
{
   id __weak obj_;//由__strong變成了__weak
}

- (void)setObject:(id __strong)obj;
@end

這樣一來母市,二者就只是弱引用對方了:


圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》
<3> __weak內(nèi)部實現(xiàn)
{
    id __weak obj1 = obj;
}

編譯器的模擬代碼:

id obj1;
objc_initWeak(&obj1,obj);//初始化附有__weak的變量
id tmp = objc_loadWeakRetained(&obj1);//取出附有__weak修飾符變量所引用的對象并retain
objc_autorelease(tmp);//將對象注冊到autoreleasepool中
objc_destroyWeak(&obj1);//釋放附有__weak的變量

使用附有__weak修飾符的變量,即是使用注冊到autoreleasepool中的對象损趋。

其中患久,objc_initWeak函數(shù)初始化附有__weak的變量。將附有__weak修飾符的變量初始化為0后浑槽,會將賦值的對象作為參數(shù)調(diào)用objc_storeWeak函數(shù),將obj對象以&obj1作為key放入一個weak表(Hash)中蒋失。

obj1 = 0;
objc_storeWeak(&obj1, obj);

objc_destroyWeak函數(shù)釋放附有__weak的變量。將0作為參數(shù)調(diào)用objc_storeWeak函數(shù),在weak表中查詢&obj1這個鍵桐玻,將這個鍵從weak表中刪除篙挽。

objc_storeWeak(%obj1, 0);
<4> __weak內(nèi)部實現(xiàn)的總結(jié)
  • 通過objc_initWeak函數(shù)初始化附有__weak修飾符的變量,在變量作用域結(jié)束時通過objc_destroyWeak函數(shù)釋放該變量畸冲。

  • objc_destroyWeak函數(shù)把第二參數(shù)的賦值對象的地址作為鍵值嫉髓,將第一參數(shù)的附有__weak修飾符的變量的地址注冊到weak表中。如果第二參數(shù)為0邑闲,則把變量的地址從weak表中刪除算行。

因為同一個對象可以賦值給多個附有__weak的變量中,所以對于同一個鍵值苫耸,可以注冊多個變量的地址州邢。

當一個對象不再被任何人持有,則需要釋放它褪子,其過程為:

  • objc_dealloc
  • dealloc
  • _objc_rootDealloc
  • objc_dispose
  • objc_destructInstance
  • objc_clear_deallocating
  • 從weak表中獲取廢棄對象的地址
  • 將包含在記錄中的所有附有__weak修飾符變量的地址賦值為nil
  • 從weak表中刪除該記錄
  • 從引用計數(shù)表中刪除廢棄對象的地址
  • 因為附有__weak修飾符變量所引起的對象像這樣被注冊到autoreleasepool中量淌,所以在@autoreleasepool塊結(jié)束之前都可以放心使用。
  • 如果大量地使用附有__weak修飾符的變量嫌褪,注冊到autoreleasepool的對象也會大量地增加呀枢,因此在使用附有__weak修飾符的變量時,最好先暫時賦值給附有__strong修飾符的變量后再使用笼痛。

__autoreleasingd修飾符

<1>__autoreleasing使用方法

  • ARC下裙秋,可以用@autoreleasepool來替代NSAutoreleasePool類對象琅拌,用__autoreleasing修飾符修飾變量來替代ARC無效時調(diào)用對象的autorelease方法(對象被注冊到autoreleasepool)。


    圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》
  • 不需要顯式地附加__autoreleasing修飾符摘刑,因為編譯器會檢查方法名是否已alloc/new/copy/mutableCopy方法開始进宝,如果不是則自動將返回值的對象注冊到autoreleasePool。
@autoreleasePool{
    id __strong obj = [NSMutableArray array];
    /*
      obj變量持有對象的強引用
     并且該對象由編譯器判斷其方法名后枷恕,
     自動注冊到autoreleasePool
     */
}
/ * obj變量超出其作用域党晋,強引用失效
   所以自動釋放自己持有的對象 
  同時,隨著@autoreleasePool塊的結(jié)束徐块,
   注冊到autoreleasePool中的所有對象被自動釋放
  因為對象的所有者不存在未玻,所以廢棄該對象
  */
  • 訪問附有__weak修飾符的變量時,實際上必定要訪問注冊到autoreleasePool的對象蛹锰。
 id  __weak obj1 = obj0;
 NSLog(@"class = %@",[obj1 class]);

等同于:

id __weak obj1 = obj0;
id __autoreleasing tmp = obj1;
NSLog(@"class = %@",[tmp class]);//實際訪問的是注冊到自動個釋放池的對象
注意一下兩段等效的代碼里深胳,NSLog語句里面訪問的對象是不一樣的,它說明:
在訪問\_\_weak修飾符的變量(obj1)時必須訪問注冊到autoreleasepool的對象(tmp)铜犬。
為什么呢舞终?
因為\_\_weak修飾符只持有對象的弱引用,也就是說在將來訪問這個對象的時候癣猾,無法保證它是否還沒有被廢棄敛劝。
因此,如果把這個對象注冊到autoreleasepool中纷宇,那么在@autoreleasepool塊結(jié)束之前都能確保該對象存在夸盟。
  • id的指針或?qū)ο蟮闹羔樤跊]有顯式指定時會被附加上__autoreleasing修飾符,如下:


    圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》
  • 為了得到詳細的錯誤信息像捶,經(jīng)常在方法的參數(shù)中傳遞NSError對象的指針上陕,而不是函數(shù)返回值。

// 方法聲明
- (Bool)performOperationWithError:(NSError **)error;
// 等同于
- (Bool)performOperationWithError:(NSError *__autoreleasing *)error;

// 應用
NSError *error = nil; 
Bool result = [obj performOperationWithError:&error];

// 上述源代碼經(jīng)過編譯器的轉(zhuǎn)化:
NSError __strong *error = nil;
NSError __autoreleasing *tmp = error;
Bool result = [obj performOperationWithError:&tmp];
error = tmp;

/*
 編譯器正是通過這種添加源代碼的方式使得原先的源代碼即不會編譯出錯拓春,也能在使用  參數(shù)取得對象時释簿,貫徹內(nèi)存管理的思考方式。
 */
  • 在顯式地指定__autoreleasing修飾符時硼莽,必須注意對象變量要為自動變量(包括局部變量庶溶、函數(shù)以及方法參數(shù))

<2> __autoreleasing內(nèi)部實現(xiàn)

將對象賦值給附有__autoreleasing修飾符的變量等同于ARC無效時調(diào)用對象的autorelease方法。

alloc/new/copy/mutableCopy方法群:

@autoreleasepool{
    id __autoreleasing obj = [[NSObject alloc] init];
}

編譯器的模擬代碼:

id pool = objc_autoreleasePoolPush();//pool入棧
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_autorelease(obj);// 將對象添加到autoreleasepool中
objc_autoreleasePoolPop(pool);//pool出棧

NSMutableArray類的array類方法:

@autoreleasepool{
    id __autoreleasing obj = [NSMutableArray array];
}

編譯器的模擬代碼:

id pool = objc_autoreleasePoolPush();//pool入棧
id obj = objc_msgSend(NSMutableArray, @selctor(array));
objc_retainAutoreleasedReturnValue(obj);//用于自己持有(retain)對象
objc_autorelease(obj); // 將對象添加到autoreleasepool中
objc_autoreleasePoolPop(pool);//pool出棧

在這里我們可以看到pool入棧懂鸵,執(zhí)行autorelease偏螺,出棧的三個方法。

1.3.3 ARC的規(guī)則

我們知道了在ARC機制下編譯器會幫助我們管理內(nèi)存匆光,但是在編譯期套像,我們還是要遵守一些規(guī)則:

  1. 不能使用retain/release/retainCount/autorelease
  2. 不能使用NSAllocateObject/NSDeallocateObject
  3. 必須遵守內(nèi)存管理的方法名規(guī)則
  4. 不要顯式調(diào)用dealloc
  5. 使用@autorelease塊代替NSAutoreleasePool
  6. 不能使用區(qū)域(NSZone)
  7. 對象型變量不能作為C語言結(jié)構(gòu)體的成員
  8. 顯式轉(zhuǎn)換id和void*

1. 不能使用retain/release/retainCount/autorelease

在ARC機制下使用retain/release/retainCount/autorelease方法,會導致編譯器報錯终息。

2. 不能使用NSAllocateObject/NSDeallocateObject

在ARC機制下使用NSAllocateObject/NSDeallocateObject方法凉夯,會導致編譯器報錯

3. 必須遵守內(nèi)存管理的方法名規(guī)則

對象的生成/持有的方法必須遵循以下命名規(guī)則:

  • alloc
  • new
  • copy
  • mutableCopy
  • init

對于init方法的要求則更為嚴格:

  • 必須是實例方法
  • 必須返回對象
  • 返回對象的類型必須是id類型或方法聲明類的對象類型

4. 不要顯式調(diào)用dealloc

對象被廢棄時货葬,無論ARC是否有效,系統(tǒng)都會調(diào)用對象的dealloc方法劲够。

我們只能在dealloc方法里寫一些對象被廢棄時需要進行的操作(例如移除已經(jīng)注冊的觀察者對象)但是不能手動調(diào)用dealloc方法。

注意在ARC無效的時候休傍,還需要調(diào)用[super dealloc]:

- (void)dealloc
{
    //該對象的處理
    [super dealloc];
}

5. 使用@autorelease塊代替NSAutoreleasePool

ARC下須使用使用@autorelease塊代替NSAutoreleasePool征绎。

6. 不能使用區(qū)域(NSZone)

NSZone已經(jīng)在目前的運行時系統(tǒng)(OBC2被設定的環(huán)境)被忽略了。

7. 對象型變量不能作為C語言結(jié)構(gòu)體(struct/union)的成員

C語言的結(jié)構(gòu)體如果存在Objective-C對象型變量磨取,便會引起錯誤人柿,因為C語言在規(guī)約上沒有方法來管理結(jié)構(gòu)體成員的生存周期 。

要把對象型變量加入到結(jié)構(gòu)體成員中時忙厌,可強制轉(zhuǎn)換為void * 或是附加__unsafe_unretained修飾符

8. 顯式轉(zhuǎn)換id和void*

非ARC下凫岖,這兩個類型是可以直接賦值的

id obj = [NSObject alloc] init];
void *p = obj;
id o = p;

但是在ARC下就會引起編譯錯誤。為了避免錯誤逢净,我們需要通過__bridege來轉(zhuǎn)換(單純地賦值)哥放。

id obj = [[NSObject alloc] init];
void *p = (__bridge void*)obj;//顯式轉(zhuǎn)換
id o = (__bridge id)p;//顯式轉(zhuǎn)換

__bridge_retained轉(zhuǎn)換可使要轉(zhuǎn)換賦值的變量也持有所賦值的對象。與retain類似爹土。

void *p = 0;
{
    id obj = [[NSObject alloc] init]; // 對象的retaidedCount : 1

    p = (__bridge_retained void *)obj; // 對象的retainedCount : 2

}
// 作用域結(jié)束時甥雕,obj變量持有對象的強引用失效,所以釋放持有對象胀茵,但是由于__bridge_retained轉(zhuǎn)換使變量p看上去處于持有該對象的狀態(tài)社露,因此該對象不會被廢棄
NSLog(@"class = %@", [(__bridge id)p class]);

__bridge_transfer,被轉(zhuǎn)換的變量所持有的對象在該變量被賦值給轉(zhuǎn)換目標變量后隨之釋放琼娘。與release類似峭弟。

1.3.5 屬性

當ARC有效時,屬性聲明的屬性與所有權(quán)修飾符的對應關系脱拼,如下表:


圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》

其中unsafe_unretained: unsafe_unretained表示存取方法會直接為實例變量賦值瞒瘸。

這里的“unsafe”是相對于weak而言的。我們知道weak指向的對象被銷毀時挪拟,指針會自動設置為nil挨务。而__unsafe_unretained卻不會,而是成為空指針玉组。需要注意的是:當處理非對象屬性的時候就不會出現(xiàn)空指針的問題谎柄。

后記

這是《Objective-C高級編程》的第一部分,講述了Objective-C的內(nèi)存管理機制惯雳。通過對引用計數(shù)的增減來管理內(nèi)存朝巫。ARC和非ARC機制下的內(nèi)存管理思想是一致的。在ARC機制下石景,編譯器就可以自動進行內(nèi)存管理劈猿,減少了開發(fā)的工作量拙吉。
但我們有時仍需要四種所有權(quán)修飾符來配合ARC來進行內(nèi)存管理。

__strong修飾符是id類型和對象類型默認的所有權(quán)修飾符揪荣。


__weak修飾符大多解決的是由__strong修飾符造成的循環(huán)引用的問題筷黔。


__autoreleasing修飾符修飾變量來替代ARC無效時調(diào)用對象的autorelease方法(對象被注冊到autoreleasepool)。不需要顯式地附加__autoreleasing修飾符仗颈。只有在id的指針或?qū)ο蟮闹羔樤跊]有顯式指定時會被附加上__autoreleasing修飾符佛舱,例如:為了得到詳細的錯誤信息,經(jīng)常在方法的參數(shù)中傳遞NSError對象的指針


__unsafe_unretained指向的對象被銷毀時不會自動設置為nil而是成為空指針挨决。當處理非對象屬性的時候就不會出現(xiàn)空指針的問題请祖。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市脖祈,隨后出現(xiàn)的幾起案子肆捕,更是在濱河造成了極大的恐慌,老刑警劉巖盖高,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件慎陵,死亡現(xiàn)場離奇詭異,居然都是意外死亡或舞,警方通過查閱死者的電腦和手機荆姆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來映凳,“玉大人胆筒,你說我怎么就攤上這事≌┩悖” “怎么了仆救?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長矫渔。 經(jīng)常有香客問我彤蔽,道長,這世上最難降的妖魔是什么庙洼? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任顿痪,我火速辦了婚禮,結(jié)果婚禮上油够,老公的妹妹穿的比我還像新娘蚁袭。我一直安慰自己,他們只是感情好石咬,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布揩悄。 她就那樣靜靜地躺著,像睡著了一般鬼悠。 火紅的嫁衣襯著肌膚如雪删性。 梳的紋絲不亂的頭發(fā)上亏娜,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天,我揣著相機與錄音蹬挺,去河邊找鬼维贺。 笑死,一個胖子當著我的面吹牛汗侵,可吹牛的內(nèi)容都是我干的幸缕。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼晰韵,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了熟妓?” 一聲冷哼從身側(cè)響起雪猪,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎起愈,沒想到半個月后只恨,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡抬虽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年官觅,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片阐污。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡休涤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出笛辟,到底是詐尸還是另有隱情功氨,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布手幢,位于F島的核電站捷凄,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏围来。R本人自食惡果不足惜跺涤,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望监透。 院中可真熱鬧桶错,春花似錦、人聲如沸才漆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽醇滥。三九已至黎比,卻和暖如春超营,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背阅虫。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工演闭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人颓帝。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓米碰,卻偏偏與公主長得像,于是被迫代替她去往敵國和親购城。 傳聞我的和親對象是個殘疾皇子吕座,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容