天天看點

記憶體管理:自動引用計數(ARC)1. 引用計數式的記憶體管理2. ARC 規則3. ARC 的實作

記憶體管理:自動引用計數(ARC)

  • 1. 引用計數式的記憶體管理
    • 1.1 記憶體管理方法
    • 1.2 引用計數的内部實作
    • 1.3 autorelease 的使用與實作
      • 1.3.1 使用方法
      • 1.3.2 内部實作
  • 2. ARC 規則
    • 2.1 所有權修飾符
      • 2.1.1 __strong 修飾符
      • 2.1.2 __weak 修飾符
      • 2.1.3 __autoreleasing 修飾符
        • 隐式附加的情形
          • 1. alloc/new/copy/mutableCopy 方法群
          • 2. 通路 __weak 變量
          • 3. 對象指針
      • 2.1.4 __unsafe_unretained 修飾符
    • 2.2 編碼規則
    • 2.3 屬性
  • 3. ARC 的實作
    • 3.1 __strong 修飾符
    • 3.2 __weak 修飾符
      • 3.2.1 __weak 變量引用的對象被廢棄後,将 `nil` 賦給該變量
      • 3.2.2 使用 __weak 變量,即是使用注冊到 `autoreleasepool` 中的對象
    • 3.3 __autoreleasing 修飾符

自動引用計數(ARC,Automatic Reference Counting)指記憶體管理中對引用采用自動計數的技術。在 Objective-C 采用 ARC 機制後,編譯器将自動進行記憶體管理,無需再次鍵入 retain 或 release 代碼,可以降低程式崩潰、記憶體洩露的風險并減少代碼量。

1. 引用計數式的記憶體管理

1.1 記憶體管理方法

引用計數式記憶體管理的四個原則:

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

與 Objective-C 方法對應:

對象操作 Objecitve-C 方法
生成并持有對象 以 alloc/new/copy/mutableCopy 開頭的方法(駝峰命名)
持有對象 retain 方法
釋放對象 release 方法
廢棄對象 dealloc 方法

這些方法包含 NSObject 類中,使用以上方法進行記憶體管理:

//自己生成并持有對象
id obj = [[NSObject alloc] init]; 


// 取得非自己生成的對象,且自己不持有對象
id obj = [NSMutableArray array];  

// 自己持有對象
[obj retain];


// 釋放對象
[obj release];
           

用某個方法生成對象,并将其傳回給調用方:

- (id) object{
	id obj = [[NSObject alloc] init];  // 自己生成并持有對象
	[obj autorelease]; // 将該對象注冊到 releasepool 中
	return obj;
}
           

使用

autorelease

方法可以使對象在超出指定的生存範圍時能夠自動并正确的釋放。

原理:将對象注冊到

autoreleasePool

中,當 pool 廢棄時會自動調用

release

釋放 pool 中的所有對象。

1.2 引用計數的内部實作

系統在運作時維護一張散清單(引用計數表)來管理引用計數。表的 key 值為記憶體塊位址的散列值,value 為該對象的引用計數。

  • 生成對象:在散清單中建立新的一行,記錄新對象的引用計數。
  • 持有對象:查找對象對應的行,該行的值 + 1。
  • 釋放對象:查找對象對應的行,該行的值 - 1。如果該行的值已經為 1,則銷毀對象。

好處:

  • 對象用記憶體塊的配置設定無需考慮記憶體塊頭部。
  • 引用計數表各記錄中存有記憶體塊位址,可從各個記錄追溯到哥對象的記憶體塊。即使出現故障導緻對象的記憶體塊損壞,隻要引用計數表沒有被破壞,依然可以找到記憶體塊的位置。

1.3 autorelease 的使用與實作

1.3.1 使用方法

  1. 生成并持有

    NSAutoreleasePool

    對象;
  2. 調用已配置設定對象的

    autorelease

    執行個體方法;
  3. 廢棄

    NSAutoreleasePool

    對象。

對于所有調用過

autorelease

的對象,在廢棄

NSAutoreleasePool

對象時,都将調用

release

方法。

1.3.2 内部實作

[obj autorelease]

本質上是調用

[NSAutoreleasePool addObject: obj]

autoreleasePool

會通過類似數組的資料結構持有對象。

autoreleasePool

的組織形式可以類比為函數調用棧的形式。系統内部由名為

AutorelesePoolPage

的類來維護

autoreleasePool

的棧。當建立一個

autoreleasePool

時,相當于入棧;廢棄一個

autoreleasePool

時,會進行出棧。

2. ARC 規則

ARC 的本質部分是與 引用計數式記憶體管理一樣的,隻是 ARC 自動地幫助我們處理“引用計數”的相關部分。

2.1 所有權修飾符

Objective-C 中的對象類型必須附加所有權修飾符。

2.1.1 __strong 修飾符

__strong

是對象類型的預設修飾符。

附有

__strong

修飾符的變量在超出其作用域時,會釋放其被賦予的對象。

__strong

修飾符表示對對象的“強引用”。持有強引用的變量在超出其作用域時被廢棄,随着強引用的失效,引用的對象會随之釋放。

id __strong obj = [[NSObject alloc] init];

// ARC 無效
{
	id obj = [[NSObject alloc] init];
	...
	[obj release];
}

// 示例
id __strong obj1 = [[NSObject alloc] init]; // obj1 持有 對象 A
id __strong obj2 = [[NSObject alloc] init]; // obj2 持有 對象 B
obj1 = obj2; // obj1 持有對象 B,對象 A 沒有所有者被廢棄。
           

強引用的對象變量在指派時會持有對象,通過

__strong

修飾符,可以不必再使用

retain

release

方法。

另外,

__strong

__weak

__autoreleasing

修飾符會保證附有這些修飾符的自動變量初始化為

nil

2.1.2 __weak 修飾符

引用計數式管理中會發生“循環引用”的問題,引起記憶體洩漏,即應當廢棄的對象在超出其生存周期後繼續存在。

使用

__weak

修飾符可以避免循環引用,

__weak

修飾符提供弱引用,不能持有對象執行個體。

在持有某對象的弱引用時,若該對象被廢棄,則此弱引用将自動失效且指派為

nil

。通過檢查

__weak

的變量可以判斷被指派的對象是否已被廢棄。

2.1.3 __autoreleasing 修飾符

ARC無效時,使用

autorelease

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	id obj = [[NSObject alloc] init];
	[obj autorelease];
	[pool drain];
           

使用ARC,該代碼寫成:

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

即指定“

@autoreleasingpool

塊”來替代“

NSAutoreleasePool

對象生存、持有以及廢棄”這一範圍。

ARC中,通過将對象指派給附加了

__autoreleasing

修飾符的變量來替代調用

autorelease

方法,即将對象被注冊到

autoreleasepool

隐式附加的情形

通常,很少會需要顯式地附加

__autoreleasing

。因為編譯器在一些情形下會隐式附加

__autoreleasing

修飾符。

1. alloc/new/copy/mutableCopy 方法群

編譯器會檢查方法名是否以

alloc/new/copy/mutableCopy

開始,如果不是則自動将傳回值的對象注冊到

autoreleasepool

。(

init

方法的傳回值不會注冊到

autoreleasepool

對象所有權狀況:

@autoreleasepool {
		id __strong obj = [NSMutableArray array];
		// obj 為強引用,自己持有對象。 并且該對象由編譯器判斷其方法名後,自動注冊到 autoreleasepool
	}
	// obj 超出作用域,強引用失效,自動釋放自己持有的對象。
	// @autoreleasepool 塊結束,注冊到 pool 中的所有對象被自動釋放。
	// 對象沒有所有者,廢棄對象
           
2. 通路 __weak 變量

在通路附有

__weak

修飾符的變量時,實際上是通路注冊到

autoreleasepool

的對象。

因為

__weak

隻持有對象的弱引用,而在通路的過程中,該對象有可能被廢棄,是以把要通路的對象注冊到

autoreleasepool

中,在

@autoreleasepool

塊結束前都能確定該對象存在。

3. 對象指針

對象的指針在沒有顯示指定時會自動附上

__autoreleasing

id *obj

/

NSObject **obj

會自動附加

__autoreleasing

變成

id __autoreleasing *obj

/

NSObject * __autoreleasing *obj

指派給對象指針時,所有權修飾符必須一緻。

在使用參數取得對象時,将參數聲明為附有

__autoreleasing

修飾符的對象指針類型。

另外,在顯式指定

__autoreleasing

修飾符時,對象必須要是自動變量(包括局部變量、函數以及方法參數)。

autoreleasepool

的調試用函數

_objc_autoreleasePoolPrint()

,利用這一函數可調試注冊到

autoreleasepool

上的對象。

2.1.4 __unsafe_unretained 修飾符

附有

__unsafe_unretained

修飾符的變量不屬于編譯器的記憶體管理對象。

主要用于 iOS4 及 OS X Snow Leopard 的應用程式中。

在使用

__unsafe_unretained

修飾符時,指派給

__strong

變量時需要確定被指派的對象确實存在。

2.2 編碼規則

在 ARC 有效的情況下必須遵守一定的規則:

  • 不能使用 retain/release/retainCount/autorelease
  • 不能使用 NSAllocateObject/NSDeallocateObject
  • 須遵守記憶體管理方法的命名規則

    追加了

    init

    的命名規則,該方法必須是執行個體方法,并且必須要傳回該方法聲明類的對象類型或超類或子類,該傳回對象并不注冊到 autoreleasepool 上。
  • 不要顯式地調用 dealloc
  • 使用 @autoreleasepoll 塊替代 NSAutoreleasePool
  • 不能使用 NSZone
  • 對象型變量不能作為 C 語言結構體的成員

    可強制轉換為

    void *

    或附加

    __unsafe_unretained

    修飾符,但要注意記憶體管理。
  • 顯式轉換

    id

    void *

    使用

    __bridge

    能夠互相轉換,多使用在 Objective-C 對象與 Core Foundation 對象之間的互相轉換中。

2.3 屬性

屬性聲明的屬性與所有權修飾符的對應關系

屬性聲明 所有權修飾符
assign __unsafe_unretained
copy __strong(但是指派的是被複制的對象)
retain __strong
strong __strong
unsafe_unretained unsafe_unretained
weak __weak

3. ARC 的實作

3.1 __strong 修飾符

指派給

__strong

修飾符的變量在編譯後轉換為以下代碼:

id __strong obj = [[NSObject alloc] init];

	// 編譯器的模拟代碼
	id obj = objc_msgSend(NSObject, @selector(alloc));
	objc_msgSend(obj, @selector(init));
	objc_release(obj);
           

在調用

alloc/new/copy/mutableCopy

以外的方法時,編譯器會插入

objc_retainAutoreleasedReturnValue(obj)

函數,用于持有從方法中傳回的對象:

id __strong obj = [NSMutableArray array];

	// 編譯器的模拟代碼
	id obj = objc_msgSend(NSMutableArray, @selector(array));
	objc_retainAutoreleasedReturnValue(obj);
	objc_release(obj);
           

與之相對應的函數是

objc_autoreleaseReturnValue

,用于

alloc/new/copy/mutableCopy

以外的傳回對象的方法中。即使用

objc_autoreleaseReturnValue(obj)

函數傳回注冊到

autoreleasepool

中的對象。

+ (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(obj)

函數會檢查使用該函數的方法或函數調用方的執行指令清單,如果方法或函數的調用方在調用了方法或函數後緊接着調用

objc_retainAutoreleasedReturnValue(obj)

,那麼就不将傳回的對象注冊到

autoreleasepool

中,而是直接傳遞給方法或函數的調用方。

通過這兩個函數的協作,可以不将對象注冊到

autoreleasepool

中而直接傳遞,優化性能。

3.2 __weak 修飾符

__weak

修飾符主要提供兩種功能,以下解析這兩種功能的實作。

3.2.1 __weak 變量引用的對象被廢棄後,将

nil

賦給該變量

編譯器對

__weak

變量指派的處理:

id __weak weakObj = obj;

	// 編譯器的模拟代碼
	id weakObj;
	objc_initWeak(&weakObj, obj);
	objc_destroyWeak(&weakObj);
           

通過

objc_initWeak

函數初始化

__weak

變量,在變量作用域結束時通過

objc_destroyWeak

函數釋放該變量。

objc_initWeak

函數調用下列代碼,将

__weak

變量初始化為 0 後,将指派的對象作為參數調用

objc_storeWeak

函數。

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

objc_destroyWeak

函數将 0 作為參數調用

objc_storeWeak

函數。

objc_storeWeak

函數把傳入第二個參數的對象位址作為

key

,将第一個參數即

__weak

變量的位址作為

value

存入 weak 表 中。如果第二個參數為 0,則将該位址從 weak 表 中删除。

weak表與引用計數表相同,都是散清單。

使用 weak 表,将已廢棄對象的位址作為

key

值進行檢索,即可高速擷取對應的

__weak

變量的位址。

另外,由于一個對象可同時指派給多個

__weak

變量,是以同一個

key

值可注冊多個

__weak

變量的位址。

釋放對象時的動作
  1. objc_release
  2. 引用計數為 0,執行 dealloc
  3. _objc_rootDealloc
  4. object_dispose
  5. objc_destrcutInstance
  6. objc_clear_deallocating

對象被廢棄時最後會調用

objc_clear_deallocating

函數,邏輯如下:

  1. 從 weak 表 中以廢棄對象的位址為

    key

    檢索對應的

    value

  2. 将檢索到的所有

    value

    __weak

    變量的位址,指派為

    nil

  3. 從 weak 表 中删除該記錄;
  4. 從引用計數表中以廢棄對象的位址為

    key

    ,删除對應記錄。

通過以上邏輯即可實作

__weak

變量引用的對象被廢棄後,将

nil

賦給該變量。

是以,如果大量使用

__weak

變量,會消耗 CPU 資源。是以盡量隻在 需要避免循環引用時使用

__weak

修飾符。

3.2.2 使用 __weak 變量,即是使用注冊到

autoreleasepool

中的對象

編譯器将轉換下面代碼:

id __weak weakObj = obj;
	NSLog(@"%@", weakObj);

	// 編譯器的模拟代碼
	id weakObj;
	objc_initWeak(&weakObj,obj);
	id tmp = objc_loadWeakRetained(&weakObj);
	objc_autorelease(tmp);
	NSLog(@"%@", tmp);
	objc_destroyWeak(&weakObj);
           
  • objc_loadWeakRetained

    :取出

    __weak

    變量引用的對象并

    retain

    ;
  • objc_autorelease

    :将對象注冊到

    autoreleasepool

    ;

通過以上邏輯,

__weak

變量所引用的對象就被注冊到

autoreleasepool

中,是以在

@autoreleasepool

塊結束前都可以放心使用。

但是,如果大量使用

__weak

變量,注冊到

autoreleasepool

中的對象也會大量增加。是以,在使用

__weak

變量時,最好先暫時指派給

__strong

修飾符的變量後再使用。

id __weak o = obj;
	NSLog(@"1 %@", o);
	NSLog(@"2 %@", o);
	NSLog(@"3 %@", o);
	//上述代碼會将對象在 autoreleasepool 中注冊 5 次。

	id tmp = o;
	NSLog(@"1 %@", tmp);
	NSLog(@"2 %@", tmp);
	NSLog(@"3 %@", tmp);
	// 先賦給 __strong 變量,此時僅注冊了 1 次。
           

存在一些不支援 __weak 修飾的類,這些類重寫了 retain/release 方法并實作獨自的引用計數機制,若是将這些類對象指派給 __weak 變量,一旦編譯器檢驗出來會報出編譯錯誤,且不支援 __weak 的類極為罕見。

當 allowWeakReference / retainWeakReference 傳回 NO 時,也不能使用 __weak 修飾符。

3.3 __autoreleasing 修飾符

将對象指派給

__autoreleasing

變量等同于 ARC 無效時調用對象的

autorelease

方法。

id __autoreleasing obj = [[NSObject alloc] init];

	// 編譯器的模拟代碼
	id pool = objc_autoreleasePoolPush();
	id obj = objc_msgSend(NSObject, @selector(alloc));
	objc_msgSend(obj, @selector(init));
	objc_autorelease(obj);
	objc_autoreleasePoolPop(pool);
           

對于

alloc/new/copy/mutableCopy

方法群之外的方法,改用

objc_retainAutoreleasedReturnValue

函數持有對象。