Objective-C 2.0增加了一些新的東西,包括屬性和垃圾回收。那麼,我們在學習Objective-C 2.0之前,最好應該先了解,從前是什麼樣的,為什麼Objective-C 2.0要增加這些支援。
這一切都跟Cocoa記憶體的管理規則有關係,我們知道,Objective-C中所有變數都定義為指標。指標是一個特殊的變數,它裡面儲存的數值被解釋成為記憶體裡的一個地址,如果使用不當,就會出錯或者造成記憶體的洩露。要了解這些,就需要看看其記憶體管理的規則到底是什麼樣的。
這篇文章也應該做為蘋果開發工具中提供的效能除錯工具Instruments使用前必讀知識進行閱讀。Cocoa China將在稍後提供Instruments工具的使用方法,以及Objective-C 2.0的詳細介紹。
要知道,如果你使用Objective-C 2.0,那麼本文描述的大部分工作你都不需要自己去處理了。但是這並不意味著你可以不瞭解它,相反,只有你對記憶體管理規則更加了解,你才能更好地使用Objective-C 2.0帶來的便利。
本文原文作者是Mmalcolm Crawford,原文地址 這篇文章翻譯起來比較晦澀,希望您能看得懂。
當Cocoa新手在進行記憶體管理時,他們看上去總是把事情變得更為複雜。遵循幾個簡單的規則就可以把生活變得更簡單。而不遵循這些規則,他們幾乎一定會造成諸如記憶體洩露或者將訊息傳送給釋放掉的物件而出現的的執行錯誤。
Cocoa不使用垃圾回收(當然,Objective-C 2.0之後開始就使用了),你必須通過計算reference的數量進行自己的記憶體管理,使用-retain, -release和-autorelease。
方法描述
-retain
將一個物件的reference數量增加1。
-release
將一個物件的reference數量減少1。
-autorelease
在未來某些時候將reference數量減少1.
-alloc
為一個物件分配記憶體,並設定保留值數量(retain count)為1。
-copy
複製一個物件,並將其做為返回值。同時設定保留值數量(retain count)為1。
保留值數量規則
1 在一定的程式碼段中,使用-copy,-alloc和-retain的次數應該和-release,-autorelease保持一致。
2 使用便利構造方法建立的物件(比如NSString的stringWithString)可以被認為會被自動釋放。(autoreleased)
3 在使用你自己的引數例項時,需要實現-dealloc方法來釋放。
例子
-alloc / -release
- (void)printHello
{
NSString *string;
string = [[NSString alloc] initWithString:@"Hello"];
NSLog(string);
// 我們用 alloc 建立了NSString,那麼需要釋放它
[string release];
}
便利構造方法
- (void)printHello
{
NSString *string;
string = [NSString stringWithFormat:@"Hello"];
NSLog(string);
// 我們用便利構造方法建立的NSString
//我們可以認為它會被自動釋放
}
永遠使用存取方法
雖然有時候你可能會認為這很麻煩,但是如果你始終使用了存取方法,造成記憶體管理問題的麻煩將會降低很多。
如果你在程式碼例項的引數中頻繁使用-retain和-release,幾乎可以肯定你做了錯誤的事情。
例子
假設我們希望設定一個Counter物件的數量值。
@interface Counter : NSObject
{
NSNumber *count;
}
為了獲取和設定count值,我們定義兩個存取方法:
- (NSNumber *)count
{
return count;
// 無需retain或者release,
// 僅僅傳遞數值
}
- (void)setCount:(NSNumber *)newCount
{
// newCount值會被自動釋放,那麼我們希望保留這個newCount
// 所以需要在這裡retain。
[newCount retain];
// 由於我們在這個方法中僅僅改變了計算數量的物件,我們可以在這裡先釋放它。因為[nil release]在objective-c中也是允許的,所以即使count值沒有被指定,也可以這樣呼叫。
//我們必須在[newCount retain]之後再釋放count,因為有可能這兩個物件的指標是同一個。我們不希望不小心釋放它。
[count release];
// 重新指定
count = newCount;
}
命名約定
注意存取方法的命名約定遵循一個模式: -引數名 和 -set引數名。
遵循這一約定,會使你的程式碼可讀性更強,而且,更重要地是你可以在後面使用key-value編碼。(參閱NSKeyValueCoding協議)。
由於我們有一個物件例項引數,我們必須實現一個釋放方法:
- (void)dealloc
{
[self setCount:nil];
[super dealloc];
}
假設我們希望實現一個方法重置計數器,我們會有很多選擇。在最開始,我們使用了一個 便利構造方法,所以我們假設新的數值是自動釋放的。我們不需要傳送任何retain或者release訊息。
- (void)reset
{
NSNumber *zero = [NSNumber numberWithInt:0];
[self setCount:zero];
}
然而,如果我們使用-alloc方法建立的NSNumber例項,那我們必須同時使用一個-release。
- (void)reset
{
NSNumber *zero = [[NSNumber alloc] initWithInt:0];
[self setCount:zero];
[zero release];
}
常見錯誤
在簡單的情況下,以下程式碼幾乎一定可以正常執行,但是由於可能沒有使用存取方法,下面的程式碼在某些情況下幾乎一定會出問題。
錯誤-沒有使用存取方法
- (void)reset
{
NSNumber *zero = [[NSNumber alloc] initWithInt:0];
[count release]
count = zero;
}
錯誤-例項洩露
- (void)reset
{
NSNumber *zero = [[NSNumber alloc] initWithInt:0];
[self setCount:zero];
}
新建的NSNumber數值數量是1(通過alloc),而我們在這個方法裡沒有發出-release訊息。那麼這個NSNumber就永遠不會被釋放了,這樣就會造成記憶體洩露。
錯誤-對已經釋放的例項傳送-release訊息
- (void)reset
{
NSNumber *zero = [NSNumber numberWithInt:0];
[self setCount:zero];
[zero release];
}
你隨後在存取count的時候在這裡就會出錯。這個簡便構造方法會返回一個自動釋放的物件,你無需傳送其他釋放訊息。
這樣寫程式碼意味著,由於物件已經被自動釋放,那麼當你釋放時,retain count將被減至0,物件已經不存在了。當你下次希望獲取count值時,你的訊息會發到一個不存在的物件(通常這樣你會得到一個SIGBUS 10的錯誤提示)。
經常造成混淆的情況
陣列和其他集合類
當物件被加入到陣列、字典或者集合中,集合類會將其保留。當集合被釋放的同時,物件也會收到一個釋放訊息。如果你希望寫一個建立數字陣列的例子,你可能會這麼寫:
NSMutableArray *array;
int i;
// …
for (i = 0; i < 10; i++)
{
NSNumber *n = [NSNumber numberWithInt: i];
[array addObject: n];
}
在這個例子裡,你無需保留新建的數值,因為陣列會幫你保留。
NSMutableArray *array;
int i;
// …
for (i = 0; i < 10; i++)
{
NSNumber *n = [[NSNumber alloc] initWithInt: i];
[array addObject: n];
[n release];
}
本例中,在for迴圈裡你需要給n傳送一個-release訊息,因為你需要始終在-alloc之後將n的數量保持為1。這麼做的原因是當其通過-addObject:方法被新增至陣列中時,陣列已經將其儲存起來。即使你釋放了n,但是這個數字由於已經儲存在陣列裡,所以不會被釋放。
為了瞭解這些,假設你自己就是編寫陣列類的人。你不希望接收的物件未經你同意就消失,所以你會在物件傳遞進來時,對其傳送一個-retain訊息。如果他們被刪除,你同時也要對應地傳送一個-release訊息。在你自己-dealloc時,你也要給你收到的所有物件傳送一個-release。
原文地址:http://www.cocoachina.com/b/?p=110