記憶體管理:自動引用計數(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 使用方法
- 生成并持有
對象;NSAutoreleasePool
- 調用已配置設定對象的
執行個體方法;autorelease
- 廢棄
對象。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
-
須遵守記憶體管理方法的命名規則
追加了
的命名規則,該方法必須是執行個體方法,并且必須要傳回該方法聲明類的對象類型或超類或子類,該傳回對象并不注冊到 autoreleasepool 上。init
- 不要顯式地調用 dealloc
- 使用 @autoreleasepoll 塊替代 NSAutoreleasePool
- 不能使用 NSZone
-
對象型變量不能作為 C 語言結構體的成員
可強制轉換為
或附加void *
修飾符,但要注意記憶體管理。__unsafe_unretained
- 顯式轉換
和id
使用void *
能夠互相轉換,多使用在 Objective-C 對象與 Core Foundation 對象之間的互相轉換中。__bridge
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
賦給該變量
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
變量的位址。
釋放對象時的動作
- objc_release
- 引用計數為 0,執行 dealloc
- _objc_rootDealloc
- object_dispose
- objc_destrcutInstance
- objc_clear_deallocating
對象被廢棄時最後會調用
objc_clear_deallocating
函數,邏輯如下:
- 從 weak 表 中以廢棄對象的位址為
檢索對應的key
;value
- 将檢索到的所有
即value
變量的位址,指派為__weak
;nil
- 從 weak 表 中删除該記錄;
- 從引用計數表中以廢棄對象的位址為
,删除對應記錄。key
通過以上邏輯即可實作
__weak
變量引用的對象被廢棄後,将
nil
賦給該變量。
是以,如果大量使用
__weak
變量,會消耗 CPU 資源。是以盡量隻在 需要避免循環引用時使用
__weak
修飾符。
3.2.2 使用 __weak 變量,即是使用注冊到 autoreleasepool
中的對象
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
函數持有對象。