一起學Objective-C - 內存管理

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章