GNUstep 的記憶體管理很重要, 也很簡單. 以下文章值得參考:
Objective-C 記憶體管理的基本原則是: 誰創造了物件, 誰就要負責釋放這個物件, 這個 "誰" 便是這個物件的擁有者 (Owndership). 創造物件指的是使用 alloc, 例如:
NSTextField *aTextField = [[NSTextField alloc] init].
清除物件的方法就是
[aTextField release];
其中 aTextField 只是一個指標, 指向這個記憶體位置. 因此如果直接使用 [[NSTextField alloc] init], 而沒有指定一個 NSTextField *aTextField, 則 [[NSTextField alloc] init] 所產生出來的物件便永遠無法釋放, 因為無法使用 release 的指令.
有了這個概念之後, Objective-C 記億體管理的方式就算了解了, 相當簡單. 根據這個原則, 任何物件一定要把其產生的物件給釋放. 因此往往在一個類別中, 在 -(id) init 中會產生物件, 在 - (void) dealloc 中會把所有產生的物件給清除. 如果這個物件只在單一類別中使用, 記憶體管理就簡單得多. 再強調一次, NSTextField *aTextField 這類的程式碼只是宣告, 並沒有產生物件, 只有 [NSTextField alloc] 之類的才是真正產生物件的程式碼.
在大部份的情況之中, 物件的位址會被當成 message 傳來傳去, 不論是在同一個物件之中, 或是在不同的物件之間. 當某程式接收到物件時, 因為不是物件的擁有者, 因此不用釋放所接收進來的物件, 同時也無法得知該物件什麼時候會被釋放. 如果該程式是在物件被釋放之後才便用該物件, 那便會出現錯誤, 因為物件已被釋放了, 該記憶體位置已經沒有東西. 同樣的, 當某程式當物件傳遞出去後, 也不知道接收者什麼時候會使用該物件, 如果太早釋放該物件, 則程式也會出現錯誤. 因此在 Objective-C 之中, 如果接收到了一個物件, 並希望該物件能夠一直保留而不會釋放, 則需要使用 retain 將物件留下, 因此習慣上一接收到物件後要馬上將物件 retain 起來. 如果傳遞一個物件出去, 不知道接收者什麼時候會將物件保留起來, 這時不要使用 release 這個指令, 因為 release 會在第一時間釋放物件, 而要改用 autorelease 將物件釋放, 如此接收者才會有時間將該物件保留起來.
在某一個 method 中, 如果有接收物件, 那最好將該物件保留起來, 使用完之後再釋放, 如
(void) receiver: (id) sender { [sender retain]; ........ [sender release]; }
如果在 receiver 中有再將 sender 這個物件傳送出去, 那最好使用 [sender autorelease], 而不是 [sender release], 因為 release 會馬上釋放物件, 而 autorelease 會有較長的緩衝期. 例如
- (NSArray *) receiver { NSArray *aArray = [NSArray new]; ...... return [aArray autorelease]; }
[NSArray new] 相當於 [[NSArray alloc] init].
使用 autorelease 時, 要提供一個 autorelease 緩衝區 (Autorelease Pool), 讓 Objective-C 的 Runtime 系統暫時儲存這些表面上已被釋放, 但實際上還沒有被釋放的物件. 使用 NSAutoreleasePool *aPool = [NSAutoreleasePool new]; 即可產生一個緩衝區. 等程式要結束時再用 [aPool release] 釋放緩衝區即可. Objective-C 的 Runtime 系統會自動找到新產生的緩衝區, 所以沒有需要指定物件被放到那個緩衝區的動作.
在一個類別中, 或是一個 method 中, alloc/retain 的次數一定與 release/autorelease 的次數一樣. 如果 alloc/retain 的次數多於 release/autorelease, 則會浪費記憶體 (memory leak), 如果 alloc/retain 的次數少於 release/autorelease, 則程式會出錯 (core dump). 基於這個原則, Objective-C 內部的記憶體管理方式就是計算 alloc/retain/release/autorelease 的次數. 每一個物件內部都有個計數器, 如果該物件被 alloc/retain, 則計數器加一, 如果被 release/autorelease 則減一, 當次數為零時, 表示沒有人在使用這個物件, 則將該物件從記憶體中釋放, 這便是 Objective-C 的記憶體管理方式.
有些物件會被其他物件所共享, 例如
NSArray *oldArray = [NSArray new]; NSArray *newArray = oldArray;
這時 oldArray 與 newArray 指向相同的記憶體位置, 針對 oldArray 所做的改變會影響到 newArray 的內容 (因為是相同的記憶體位置). 如果 newArray 只想取得 oldArray 當時的內容, 而不是與 oldArray 一直保持同步, 則可以使用
NSArray *newArray = [oldArray copy];
copy 如同 alloc/retain 一般, 物件的計數器次數加一.
Objective-C 的記憶體管理原則使得 Objective-C 可以使用類似 Java 的垃圾處理方式. 因此 GNUstep 中定義了一些巨集, 為這個可能性做準備. 在 NSObject.h 中可以找到 RETAIN(), RELEASE(), AUTORELEASE(), DESTORY() 等巨集的定義.
這些程式碼在 GNUstep 中常常看見. 常用的巨集有:
使用這些巨集可以提供更謹慎的記憶體管理. 常用的方式如下:
@interface MyObject: NSObject { id myData; } -(void) setMyData: (id) newData; -(id) myData; @end @implementation MyObject - (void) setMyData: (id) newData { ASSIGN(myData, newData); } - (id) myData { return myData; } - (void) dealloc { RELEASE(myData); } @end
ASSIGNCOPY() 可以用來複製物件. 每次要更新 myData 時, 記得使用 [self setMyData: newData] 或是至少使用 ASSIGN(myData, newData). 切勿使用 myData = newData. 如此一來, 便不用耽心記憶體的問題了.
在使用多執行緒時, 可以使用可變動 (mutable)的資料結構, 因為這些 GNUstep 內建的資料結構可以處理多執行的問題:
@interface MyObject: NSObject { NSMutableArray *myArray; } -(void) setMyArray: (NSArray *) newArray; -(NSArray *) myArray; @end @implementation MyObject - (id) init { myArray = [NSMutableArray new]; } - (void) setMyArray: (NSArray *) newArray { [myArray setArray: newArray]; } - (NSArray *) myArray { return myArray; } - (void) dealloc { RELEASE(myArray); } @end
可變動的資料結構使用較多的系統資源.
要注意 GNUstep 中的傳回值, 有些已經被 autorelease 了. 例如 [NSArray arrayWith...] 或 [@"A string" stringBy...]. 如果再釋於這些物件, 程式會不正常中止. 如果要保留這些物件, 要記得 retain 下來. 可以使用 ASSIGN(), 例如:
ASSIGN(myString, [@"A string", stringBy...])
使用 ASSING() 之後, 記得要在 -dealloc 中釋放