記憶體管理基本原則
記憶體管理的依循下面的基本原則
自己產生的對象,那麼既是其持有人
不是自己產生的對象,也可成為其持有人(一個對象可以被多個人持有)
如果不想持有對象的時候,必須釋放其所有權
不能釋放已不再持有所有權的對象
不管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中就需要像下面一樣處理記憶體的釋放。
1
2
3
4 - (void) dealloc
{
free(buffer_);
}
又或者是註冊的delegate對象,觀察者對象需要被刪除的時候,也是在dealloc函數中動作。
1
2
3
4 - (void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
如果在ARC無效的時候,我們還要像下面一樣,調用父類對象的dealloc函數。
1
2
3
4 - (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 檔案的時候,往往像下面這樣寫。
1
2
3
4
5
6 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。
1
2
3
4
5
6 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的對象時,將出現類似下面的編譯錯誤。
1
2
3 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 關鍵字。比如下面:
1
2
3 struct Data {
NSMutableArray __unsafe_unretained *array;
};
這樣一來,該記憶體資訊不在編譯器記憶體管理對象內,僅僅是使用而已,沒有對象的持有權。當然,對象所有權的持有人需要明確的管理他與該結構體的互動,不要引起不必要的錯誤3。
【id】與【void*】之間需要明示cast
ARC 有效時候,由於編譯器幫我們做了記憶體管理的工作,所以我們不需要太擔心。但是當與 ARC 管理以外的物件類型互動的時候,就需要特殊的轉型關鍵字,來決定所有權的歸屬問題。