Objective-C的內存管理要比Java複雜多了,很有挑戰性,它提供了三種方法:
- Explicit
有可以通過alloc, copy等方法爲對象分配內存,dealloc銷燬已分配的內存。這種方法你可以對內存管理有完全的控制力,高性能但是出錯的可能也大。
- Retain count
利用OpenStep的retain/release機制,同時使用autorelease pools來提供一定程度的自動化內存管理,這種方法提供了比較好的對內存管理的控制,但是需要仔細的遵循一些簡單的規則,這種方法效率也很高。
- Garbage collection
類似於Java的內存回收機制,但是比起Java來就差遠了,還有一定使用限制,iOS上是不支持的,所以暫時先不研究了。
話說雖然是三個方法,但是主要還是使用第二種。建議使用NSObject.h裏面的一些標準的macro來包裝etain/release/autorelease機制。
標準的OpenStep系統利用retain count來進行內存管理,當一個對象被創建時,它的retain count是1. 當對象被retain時,這個retain count增加,當對象被release時,這個retain count減少,當retain count減少到零時,對象就被銷燬了。
Coin*c = [[Coin alloc] initWithValue: 10];
// Put coin in pouch,
[c retain];// Calls 'retain' method (retain count now 2)
// Remove coin from pouch
[c release];// Calls 'release' method (retain count now 1)
// Drop in bottomless well
[c release];// Calls 'release' ... (retain count 0) then 'dealloc'
如果一個代碼塊中一個對象被創建了,它就有義務release掉這個對象,如果一個代碼塊只是簡單使用某個對象,那它就沒義務release這個對象。簡單的說,總的retain數應該等於總的releasee數。
NSString *msg = [[NSString alloc] initWithString: @"Test message."];
NSLog(msg);
// we created msg with alloc -- release it
[msg release];
Retain和release必須被用來操作對象的實例:
- (void)setFoo:(FooClass *newFoo)
{
// first, assert reference to newFoo 保證這期間,該對象不被銷燬
[newFoo retain];
// now release reference to foo (do second since maybe newFoo == foo)
[foo release];
// finally make the new assignment; old foo was released and may
// be destroyed if retain count has reached 0
foo = newFoo;
}
因爲有retain/release機制, 你甚至可以在類中使用setter方法:
- (void)resetFoo
{
FooClass *foo = [[FooClass alloc] init];
[self setFoo: foo];
// since -setFoo just retained, we can and should
// undo the retain done by alloc
[foo release];
}
例外
有些情況下前面說的關於在代碼塊中成對使用retain和release的規則並不適用,比方說實現一個container類retain了所有加入到這個container中的對象,同時在這個container被銷燬時(在某個方法中),銷燬所有它包含的對象。總的來說你要注意成對使用retain和release。
Autorelease Pools
有一種情況,當一個對象被轉移給另一個對象時,你不想在轉移的代碼里加retain,但是你又不想讓這個對象在完成轉移前被銷燬掉。OpenStep/GNUstep的解決方案是在這種情況下使用autorelease pool. 一個autorelease pool是一種特殊的機制,會retain對象一定的時間,當然這個時間肯定足夠完成對象轉移的。實現方法是調用autorelease而不是release. Autorelease首先會把這個對象放到活動的autorelease pool中(這裏retain這個對象),然後再給這個對象發一個release消息,等過一段時間之後,autorelease pool會發第二個release消息
- (NSString *) getStatus
{
NSString *status =
[[NSString alloc] initWithFormat: "Count is %d", [self getCount]];
// set to be released sometime in the future
[status autorelease];
return status;
}
如果只在本地使用的話,任何調用-getStatus的代碼都可以不需要retain這個方法的返回對象。當然,如果返回需要被保存並且以後使用的話,那就需要retain了。
...
NSString *status = [foo getStatus];
// 'status' is still being retained by the autorelease pool
NSLog(status);
return;
// status will be released automatically later
...
currentStatus = [foo getStatus];
// currentStatus is an instance variable; we do not want its value
// to be destroyed when the autorelease pool cleans up, so we
// retain it ourselves
[currentStatus retain];
Pool Management
一個autorelease pool在GNUstep GUI類中會被自動創建,然而如果你只是使用GNUstep Base類的話你就需要自己來創建了。
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
一旦一個pool被創建之後,所有的autorelease會自動找到它。如果需要關掉這個pool並release它包含的所有的對象的話,只需要release掉pool就可以。
有可能你還會需要創建多個additional pool,那樣的話autorelease會找最近創建的進行保存。
autorelease要比release慢很多,所以只在需要用的時候使用它。
避免循環Retain