內存管理基本原則
- 內存管理的依循下面的基本原則
- 自己生成的對象,那麼既是其持有者
- 不是自己生成的對象,也可成爲其持有者(一個對象可以被多個人持有)
- 如果不想持有對象的時候,必須釋放其所有權
- 不能釋放已不再持有所有權的對象不管ARC有沒有效,該原則始終存在。
所有權關鍵字
從代碼上看,有ARC的代碼和沒有ARC的代碼區別就在下面的幾個關鍵字。
類似 NSObject* 的對象類型,或者 id 類型1,當ARC有效的時候,根據具體情況,這些關鍵字必須要使用2。
- __strong
- __weak
- __unsafe_unretained
- __autoreleasing__strong是默認的修飾符。__weak修飾了一個自動nil的weak引用。 __unsafe_unretained聲明瞭一個不會自動nil的weak引用。當變量被釋放,那麼它就變成了一個野指針了。 __autoreleasing 用來修飾一個聲明爲 (id *) 的函數的參數,當函數返回值時被釋放。
接下來,我們結合下面ARC的使用準則,來看看一些使用ARC後的技術細節。
ARC使用準則
爲了比秒程序秒退的尷尬,ARC有效時,我們的代碼必須遵循下面的準則。
- 不能使用 retain/release/retainCount/autorelease
- 不能使用 NSAllocateObject/NSDeallocateObject
- 不能使用 NSZone
- 不能明示調用dealloc
- 內存管理相關的函數必須遵循命名規則
- 使用@autoreleasepool代替NSAutoreleasePool
- Objective-C 對象不能作爲C語言結構體(struct/union)的成員
- 【id】與【void*】之間需要明示cast
建議使用Objective-C的class來管理數據格式,來代替C語言的struct。不能隱式轉換 id 和 void *。
- 讓我們一個一個來分析
不能使用 retain/release/retainCount/autorelease
內存管理完全交給編譯器去做,所以之前內存相關的函數(retain/release/retainCount/autorelease)不能出現在程序中。Apple的ARC文檔中也有下面的說明。
ARC 有效後,不需要再次使用retain 和 release
如果我們在程序中使用這些函數,經得到類似下面的編譯錯誤信息。
error: ARC forbids explicit message send of ’release’ [o release]; ^ ~~~~~~~
不能使用 NSAllocateObject/NSDeallocateObject
生成並持有一個Objective-C對象的時候,往往像下面一樣使用NSObject的alloc接口函數。
id obj = [NSObject alloc];
實際上,如果我們看了GNUstep 中關於 alloc 的代碼就會明白,實際他是使用 NSAllocateObject 來生成並持有對象實例的。換言之,ARC有效的時候,NSAllocateObject函數的調用也是禁止的。如果使用,也會遇到下面的編譯錯誤。
error: ’NSAllocateObject’ is unavailable: not available in automatic reference counting mode
同樣,對象釋放時使用的 NSDeallocateObject 函數也不能使用。
不能使用 NSZone
NSZone 是什麼?NSZone 是爲了防止內存碎片而導入的一項措施。Zone 是內存管理的基本單元,系統中管理複數的Zone。系統根據對象的使用目的,尺寸,分配其所屬的Zone區域。以提高對象的訪問效率,避免不必要的內存碎 片。但是,現在的運行時系統(用編譯開關 __OBJC2__ 指定的情況下)是不支持Zone概念的。所以,不管ARC是否有效,都不能使用 NSZone。
不能明示調用dealloc
不管是否使用ARC,當對象被釋放的時候,對象的dealloc函數被調用(就像是C++中對象的析構函數)。在該函數中,需要做一些內存釋放的動作。比如,當對象中使用了malloc分配的C語言內存空間,那麼dealloc中就需要像下面一樣處理內存的釋放。
- (void) dealloc { free(buffer_); }
又或者是註冊的delegate對象,觀察者對象需要被刪除的時候,也是在dealloc函數中動作。
- (void) dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; }
如果在ARC無效的時候,我們還要像下面一樣,調用父類對象的dealloc函數。
- (void) dealloc { [super dealloc]; }
但是當ARC有效的時候,[super dealloc];的調用已經被編譯器自動執行,已經不需要我們明示調用了。如果你在代碼中還這樣寫,難免遇到下面的錯誤。
error: ARC forbids explicit message send of ’dealloc’ [super dealloc]; ^ ~~~~~~~
內存管理相關的函數必須遵循命名規則
在iPhone開發之深入淺出 (3) — ARC之前世今生中,我們知道如果是 alloc/new/copy/mutableCopy/init 開頭的函數,需要將對象所有權返回給調用端。這條規則不管ARC是否有效都應該被遵守。只是 init 開頭的函數比較特殊,他只在ARC下有要求,而且異常苛刻。
init 開始的函數只能返回id型,或者是該函數所屬的類/父類的對象類型。基本上來說,init函數是針對alloc函數的返回值,做一些初始化處理,然後再將該對象返回。比如:
id obj = [[NSObject alloc] init];
再比如下面定義的函數就是不對的:
- (void) initThisObject;
需要是下面這樣:
- (id) initWithObject:(id)obj;
另外,下面名爲 initialize 的函數比較特殊,編譯器將把它過濾掉,不按上面的規則處理。
使用@autoreleasepool代替NSAutoreleasePool
在ARC之下,已經不能在代碼中使用 NSAutoreleasePool,我們之前寫 main.m 文件的時候,往往像下面這樣寫。
int main(int argc, char *argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
int retVal = UIApplicationMain(argc, argv, nil, nil);
[pool release];
return retVal;
}
而當ARC有效後,我們需要用@autoreleasepool代替NSAutoreleasePool。
int main(int argc, char *argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
當編譯器看到 @autoreleasepool 定義的塊後會自動生成 NSAutoreleasePool 對象,並將需要的對象放入 AutoReleasePool 中,當出方塊的定義範圍時,pool 中的對象將被釋放。
Objective-C 對象不能作爲C語言結構體(struct/union)的成員
當我們設置ARC有效,並在C語言的結構體中定義Objective-C的對象時,將出現類似下面的編譯錯誤。
struct Data {
NSMutableArray *array;
};
error: ARC forbids Objective-C objs in structs or unions NSMutableArray *array; ^
由於 ARC 是將內存管理的細節委託給編譯器來做,所以說編譯器必須要管理對象的生命週期。而LLVM 3.0中不存在對單純C語言構造體成員的內存管理方法。如果單純是棧對象,利用進出棧原理,可以簡單地維護對象的生命週期;而結構體是不行的,簡單地理 解,結構體沒有析構函數,編譯器自身不能自動釋放其內部的 Objective-C 對象。
當我們必須在C語言的結構體中放入 Objective-C 對象的時候,可以使用 void* 轉型,或者使用 __unsafe_unretained 關鍵字。比如下面:
struct Data {
NSMutableArray __unsafe_unretained *array;
};
這樣一來,該內存信息不在編譯器內存管理對象內,僅僅是使用而已,沒有對象的持有權。當然,對象所有權的持有者需要明確的管理他與該結構體的交互,不要引起不必要的錯誤3。
【id】與【void*】之間需要明示cast
ARC 有效的時候,由於編譯器幫我們做了內存管理的工作,所以我們不需要太擔心。但是當與 ARC 管理以外的對象類型交互的時候,就需要特殊的轉型關鍵字,來決定所有權的歸屬問題。
主要的轉型關鍵字是:
關鍵字 | 解釋 |
---|---|
__bridge | 單純的類型轉換,沒有進行所有權的轉移 |
__bridge_retained | 類型轉換是伴隨所有權傳遞,轉換前後變量都持有對象的所有權 |
__bridge_transfer | 類型轉換伴隨所有權轉移,被轉換變量將失去對象的所有權 |
當我們在 Core Foundation 對象類型與 Objective-C 對象類型之間切換的時候,需要把握下面的因素:
- 明確被轉換類型是否是 ARC 管理的對象
- Core Foundation 對象類型不在 ARC 管理範疇內
- Cocoa Framework::Foundation 對象類型(即一般使用到的Objectie-C對象類型)在 ARC 的管理範疇內
- 如果不在 ARC 管理範疇內的對象,那麼要清楚 release 的責任應該是誰
- 各種對象的生命週期是怎樣的
題外話
Xcode 4.3帶來的變化
最近隨着 iOS 5.1 的推出,Xcode也推出了4.3版本。在該版本下,ARC 有效時的屬性(@property) 定義的時候,如果不明確指定所有權關鍵字,那麼缺省的就是 strong。而在 Xcode4.2 中,即使 strong 也要顯示指定。
在 Xcode4.2 的時候,針對下面的代碼,
// ARC 無效
@property (nonatomic, retain) NSString *string; // --->
// ARC 有效
@property (nonatomic, strong) NSString *string;
而在 Xcode 4.3 中,我們可以這麼做,
// ARC 無效
@property (nonatomic, retain) NSString *string; // --->
// ARC 有效
@property (nonatomic) NSString *string;
ARC 代碼自動變換
另外,Xcode 4.2開始,增加了舊代碼向 ARC 代碼自動轉換的功能。有興趣的朋友可以試試。位置是:
Edit->Refactor->Convert to Objective-C ARC…
爲什麼iOS中沒有GC
我們已經知道ARC並不是GC(垃圾回收)了,那麼,爲什麼iOS中不支持該機能呢?還特意搞出個ARC來。以下是我的分析:
- 消耗CPU時間的處理儘量避免,以節約電池電量
- GC執行的後,會停掉運行時庫;這是最大的心結
- 嵌入式設備本身內存就不是很大,如果GC不停的在後臺運行,執行的頻率會很高,嚴重影響性能
- UI動畫處理是iOS的一大賣點,而有了GC後可能會引起不必要的性能損失
1. 關於Objective-C對象的解釋,可以參考iPhone開發入門(7)— 從C/C++語言到Objective-C語言。
2. 當然,如果你不寫,編譯器會用缺省的值代替。具體見iPhone開發之深入淺出 (3) — ARC之前世今生中的描述。
3. 關於這一點,可以參考iPhone開發之深入淺出 (1) — ARC是什麼 一文,明白爲什麼 __unsafe_unretained 是危險的。