《Objective-C高級編程》自動引用計數

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

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

自動引用計數是指內存管理中對引用采取自動計數的技術。


“ 在LLVM編譯器中設置ARC為有效狀態,就無需再次鍵入retain或者是release代碼。”

1.2 內存管理/引用計數

1.2.1 計數的內存管理

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

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

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

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

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

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

1.2.2 內存管理的思考方式

內存管理的思考方式有以下四種:

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

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

  • 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方法實現的。所以如果想持有這個對象,需要執行retain方法才可以

不再需要自己持有的對象時釋放
  • 自己持有的對象,一旦不再需要,持有者有義務釋放該對象,務必使用release方法釋放。
注意,是有義務,而不是有權利,注意兩個詞的不同

??:

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方法也能將調用autorelease方法取得的對象變為自己持有

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


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

在釋放對象的時候,我們只能釋放已經持有的對象,非自己持有的對象是不能被自己釋放的。

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

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

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

1.2.3 alloc/retain/release/dealloc實現

借助開源軟件GNUstep的源代碼中alloc/retain/release/dealloc的實現來理解蘋果的Cocoa實現。總結如下:

- 在Objective-C的對象中存在引用計數這一整數值
- 調用alloc或是retain方法后,引用計數值加1
- 調用release方法后,引用計數值減1
- 引用計數值為0時,調用dealloc方法廢棄對象
蘋果的實現:

由于NSObject類的源代碼沒有公開,利用Xcode的調試器(lldb)和iOS大概追溯內存管理和引用計數的實現。通過追溯可以發現似乎和散列表(Hash)有關,這說明蘋果對引用計數的管理應該是通過散列表來執行的。


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

在這張表里,key為內存塊地址,而對應的值為引用計數。也就是說,它保存了這樣的信息:一些被引用的內存塊各自對應的引用計數。

那么使用散列表來管理內存有什么好處呢?

因為計數表保存內存塊地址,我們就可以通過這張表來:

  • 確認損壞內存塊的位置。


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

1.2.4 autorelease

autorelease 介紹

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

  • 生成并持有NSAutoreleasePool對象。
  • 調用已分配對象的autorelease方法。
  • 廢棄NSAutoreleasePool對象。


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

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

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

NSRunLoop在每次循環過程中,NSAutoreleasePool對象都會被生成或廢棄。

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

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

在這類情況下,有必要在適當的地方生成、持有或廢棄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多線程和內存管理》

思考:什么時候會創建自動釋放池?
答:運行循環檢測到事件并啟動后,就會創建自動釋放池,而且子線程的 runloop 默認是不工作的,無法主動創建,必須手動創建。
??:
自定義的 NSOperation 類中的 main 方法里就必須添加自動釋放池。否則在出了作用域以后,自動釋放對象會因為沒有自動釋放池去處理自己而造成內存泄露。

autorelease實現

同樣借助開源軟件GNUstep的源代碼中autorelease的實現來理解蘋果的autorelease實現。總結如下:

- autorelease實例方法的本質就是調用NSAutoreleasePool對象的addObject類方法

- addObject類方法調用正在使用的NSAutoreleasePool對象的addObject實例方法
如果嵌套生成或持有的NSAutoreleasePool對象,理所當然會使用最內側的對象

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

- drain實例方法在廢棄autorelease對象數組之前,會先對數組中的所有對象調用release實例方法

蘋果的實現:

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

1.3 ARC規則

1.3.1 內存管理的思考方式

ARC和非ARC機制下的內存管理思想是一致的:

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

在ARC機制下,編譯器就可以自動進行內存管理,減少了開發的工作量。

1.3.2 所有權修飾符

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

  • __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(現在obj0,ojb1,obj2都強引用對象B)
obj1 = nil;//obj1不再強引用對象B
obj0 = nil;//obj0不再強引用對象B
obj2 = nil;//obj2不再強引用對象B,不再有任何強引用引用對象B,對象B被廢棄
  • 附有__strong修飾符的變量之間可以相互賦值。通過相互賦值,可以使得變量對對象的強引用失效,從而釋放原先持有的對象,轉而持有由另外一個變量賦值的新的對象的強引用。
  • 即時是Objective-C類成員變量,也可以在方法參數上,使用附有__strong修飾符的變量
  • __strong修飾符可以確保將附有__strong修飾符的自動變量(局部變量)初始化為nil(該規則適用于__weak修飾符和__autoreleasing修飾符)
<1> 通過__strong修飾符使ARC有效遵循了Objective-C內存管理的思考方式
  • “自己生成的對象,自己持有”和“非自己生成的對象,自己也能持有”只需通過對帶__strong修飾符的變量賦值便可達成
  • 通過廢棄帶__strong修飾符的變量(變量作用域結束或是成員變量所屬對象廢棄)或者對變量賦值,都可以做到”不再需要自己持有的對象時釋放“
  • 由于不必再次鍵入release,所以”非自己持有的對象無法釋放“原本就不會執行 。 --- (在ARC有效時不能使用release方法的緣故是編譯器會自動插入release)
<2> __strong修飾符 是id類型和對象類型默認的所有權修飾符:
id obj = [NSObject alloc] init];

等同于

id __strong obj = [NSObject alloc] init];

其內存管理的過程:

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

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

<3> __strong內部實現:

生成并持有對象:

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

編譯器的模擬代碼:

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

使用命名規則以外的構造方法 ,如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內部實現的總結

如上所示, objc_retainAutoreleasedReturnValue函數和objc_autoreleaseReturnValue函數的協作,可以不將對象注冊到autoreleasePool中而直接傳遞,這一過程達到了最優化。

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

  • objc_autoreleaseReturnValue函數與objc_autorelease函數不同,一般不僅限于注冊對象到autoreleasePool中。

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

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

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

__weak修飾符

<1>__weak修飾符提供弱引用。弱引用不能持有對象。
  • 在持有“對象”的弱引用時,若該對象被廢棄,則此弱引用將自動失效且處于nil被賦值的狀態(空弱引用)
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修飾符大多解決的是循環引用的問題。

??:

@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多線程和內存管理》

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

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

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

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


圖片來自:《Objective-C高級編程:iOS與OS X多線程和內存管理》
<3> __weak內部實現
{
    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函數初始化附有__weak的變量。將附有__weak修飾符的變量初始化為0后,會將賦值的對象作為參數調用objc_storeWeak函數,將obj對象以&obj1作為key放入一個weak表(Hash)中。

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

objc_destroyWeak函數釋放附有__weak的變量。將0作為參數調用objc_storeWeak函數,在weak表中查詢&obj1這個鍵,將這個鍵從weak表中刪除。

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

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

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

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

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

__autoreleasingd修飾符

<1>__autoreleasing使用方法

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


    圖片來自:《Objective-C高級編程:iOS與OS X多線程和內存管理》
  • 不需要顯式地附加__autoreleasing修飾符,因為編譯器會檢查方法名是否已alloc/new/copy/mutableCopy方法開始,如果不是則自動將返回值的對象注冊到autoreleasePool。
@autoreleasePool{
    id __strong obj = [NSMutableArray array];
    /*
      obj變量持有對象的強引用
     并且該對象由編譯器判斷其方法名后,
     自動注冊到autoreleasePool
     */
}
/ * obj變量超出其作用域,強引用失效
   所以自動釋放自己持有的對象 
  同時,隨著@autoreleasePool塊的結束,
   注冊到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塊結束之前都能確保該對象存在。
  • id的指針或對象的指針在沒有顯式指定時會被附加上__autoreleasing修飾符,如下:


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

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

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

// 上述源代碼經過編譯器的轉化:
NSError __strong *error = nil;
NSError __autoreleasing *tmp = error;
Bool result = [obj performOperationWithError:&tmp];
error = tmp;

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

<2> __autoreleasing內部實現

將對象賦值給附有__autoreleasing修飾符的變量等同于ARC無效時調用對象的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入棧,執行autorelease,出棧的三個方法。

1.3.3 ARC的規則

我們知道了在ARC機制下編譯器會幫助我們管理內存,但是在編譯期,我們還是要遵守一些規則:

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

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

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

2. 不能使用NSAllocateObject/NSDeallocateObject

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

3. 必須遵守內存管理的方法名規則

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

  • alloc
  • new
  • copy
  • mutableCopy
  • init

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

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

4. 不要顯式調用dealloc

對象被廢棄時,無論ARC是否有效,系統都會調用對象的dealloc方法。

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

注意在ARC無效的時候,還需要調用[super dealloc]:

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

5. 使用@autorelease塊代替NSAutoreleasePool

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

6. 不能使用區域(NSZone)

NSZone已經在目前的運行時系統(OBC2被設定的環境)被忽略了。

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

C語言的結構體如果存在Objective-C對象型變量,便會引起錯誤,因為C語言在規約上沒有方法來管理結構體成員的生存周期 。

要把對象型變量加入到結構體成員中時,可強制轉換為void * 或是附加__unsafe_unretained修飾符

8. 顯式轉換id和void*

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

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

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

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

__bridge_retained轉換可使要轉換賦值的變量也持有所賦值的對象。與retain類似。

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

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

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

__bridge_transfer,被轉換的變量所持有的對象在該變量被賦值給轉換目標變量后隨之釋放。與release類似。

1.3.5 屬性

當ARC有效時,屬性聲明的屬性與所有權修飾符的對應關系,如下表:


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

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

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

后記

這是《Objective-C高級編程》的第一部分,講述了Objective-C的內存管理機制。通過對引用計數的增減來管理內存。ARC和非ARC機制下的內存管理思想是一致的。在ARC機制下,編譯器就可以自動進行內存管理,減少了開發的工作量。
但我們有時仍需要四種所有權修飾符來配合ARC來進行內存管理。

__strong修飾符是id類型和對象類型默認的所有權修飾符。


__weak修飾符大多解決的是由__strong修飾符造成的循環引用的問題。


__autoreleasing修飾符修飾變量來替代ARC無效時調用對象的autorelease方法(對象被注冊到autoreleasepool)。不需要顯式地附加__autoreleasing修飾符。只有在id的指針或對象的指針在沒有顯式指定時會被附加上__autoreleasing修飾符,例如:為了得到詳細的錯誤信息,經常在方法的參數中傳遞NSError對象的指針


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

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

推薦閱讀更多精彩內容