剖析Objective-C內存管理規則
詳解Objective-C 2.0 關于Objective-C內存管理規則是本文要介紹的內容,不多說,先來看內容。Objective-C 2.0增加了一些新的東西,包括屬性和垃圾回收。那么,我們在學習Objective-C 2.0之前,最好應該先了解,從前是什么樣的,為什么Objective-C 2.0要增加這些支持。
這一切都跟Cocoa內存的管理規則有關系,我們知道,Objective-C中所有變量都定義為指針。指針是一個特殊的變量,它里面存儲的數值被解釋成為內存里的一個地址,如果使用不當,就會出錯或者造成內存的泄露。要了解這些,就需要看看其內存管理的規則到底是什么樣的。
這篇文章也應該做為蘋果開發工具中提供的性能調試工具Instruments使用前必讀知識進行閱讀。要知道,如果你使用Objective-C 2.0,那么本文描述的大部分工作你都不需要自己去處理了。但是這并不意味著你可以不了解它,相反,只有你對內存管理規則更加了解,你才能更好地使用Objective-C 2.0帶來的便利。
當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。
小結:詳解Objective-C 2.0 關于Objective-C內存管理規則的內容介紹完了,希望本文對你有所幫助。