Objective-Cのメモリ管理

(2009.1.28 新規作成)

Objective-C は、ガベージコレクションがない時代のプログラミング言語です。プログラマがきちんとオブジェクトを解放するように書かなければなりません。

実装として、id 型は objc_objectへのポインタ型です。すべてのオブジェクトはmallocで確保されるイメージで、これはブロック内のローカル変数(のオブジェクト)を自動的に解放してくれるCより悪い状況です。

Objective-Cでは、自動解放プールと、ルールによるオブジェクトの解放で対処します。

gccにはライブラリが何も付いていないので、以降はGNUstepを前提とします。Fedora 10 Linux + gcc 4.3 で確認しました。

NSAutoreleasePool

オブジェクトを生成したら自動解放プールに登録するようにします。自動解放プールが解放されるときに登録されたオブジェクトが解放されます。

GNUstepの自動解放プールは NSAutoreleasePool クラスです。

Note.

C++ では、自動解放プールではなく、boost::shared_ptr を使ったり、コンテナを拡張して要素たるオブジェクトを解放したりする方法がよく使われます。

NSAutoreleasePool オブジェクトを生成すると、内部でスタックを構成します。その後、オブジェクトに autorelease メッセージを送ると、スタックトップのプールに登録されます。

NSAutoreleasePool オブジェクトを適切に生成することで、オブジェクトの解放時期をコントロールすることもできます。(入れ子にできる。)

GUIプログラムでは、イベントループが回るたびに解放処理がおこなわれます。

次の例を見てください。

objc
[RAW]
  1. #include <stdio.h>
  2. #include <Foundation/Foundation.h> // for NSAutoreleasePool
  3. @interface Foo: NSObject
  4. @end
  5. @implementation Foo
  6. -(void) dealloc {
  7. printf("%s: called.\n", __func__);
  8. [super dealloc];
  9. }
  10. @end
  11. int main() {
  12. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  13. // オブジェクトをプールに登録
  14. [[[Foo alloc] init] autorelease];
  15. [[[Foo alloc] init] autorelease];
  16. // プール解放
  17. printf("pool release...\n");
  18. [pool release];
  19. return 0;
  20. }

クラスFooのオブジェクトをautoreleaseメソッドで自動プールに登録します。プールを解放すると、Fooオブジェクトも解放されます。

実行結果:

pool release...
-[Foo dealloc]: called.
-[Foo dealloc]: called.

プールを解放するタイミングで、各オブジェクトに対してreleaseが送られます。releaseは参照カウントを減らして、必要があればdeallocを呼び出してオブジェクトを解放します。

リファレンス:

メモリ管理のルール

Objective-Cでは、言語仕様ではなく、運用ルールとして誰がオブジェクトを解放すべきか定めています。Objective-Cでプログラムを書く場合、このルールに従わなければなりません。

簡単に訳しておきます。

基礎ルール:

  • "alloc" または "new" で始まるメソッド、または "copy" を含む名前のメソッドを用いてオブジェクトを生成した場合、呼び出し側がそのオブジェクトを所有する。例えば、alloc, newObject, mutableCopy.
  • オブジェクトの retain メソッドを呼び出したときもオブジェクトを所有する。
  • オブジェクトの所有者は release または autorelease メソッドを呼び出し、オブジェクトを解放しなければならない。所有しないときは、そのオブジェクトを解放してはならない。

基礎ルールから派生するルール:

  • 基礎ルールからの必然として、所有しないオブジェクトをインスタンス変数に保存するときは、retain メソッドを呼び出すか、それをコピーしなければならない。ただし weak references の場合を除く。
  • 受け取ったオブジェクトは、通常、そのメソッド内部では妥当であり続けることが保証される。メソッドは、オブジェクトをその呼び出し元に安全に返すことができる。ただし、マルチスレッドアプリケーションなどではこの限りではない。
  • メッセージの副作用としてオブジェクトが不正となるのを防ぐために、retain と、release または autorelease の組を使うべき。
  • autorelease は、単に、「後でreleaseメッセージを送る」という意味。

次の例は、参照カウントがどのように変わっていくかを表示します。

objc
[RAW]
  1. #include <stdio.h>
  2. #include <Foundation/Foundation.h>
  3. @interface MyStr: NSObject {
  4. char* s;
  5. }
  6. // 新しいオブジェクトを作るクラスメソッド
  7. +(MyStr*) stringWithCString: (const char*) cstr;
  8. @end
  9. @implementation MyStr
  10. +(MyStr*) stringWithCString: (const char*) cstr {
  11. MyStr* p = [[MyStr alloc] init];
  12. printf("%s: count = %d\n", __func__, [p retainCount]); // => 1
  13. if (p) {
  14. p->s = strdup(cstr);
  15. // 'alloc*'でも'new*'でもない => 自分をautoreleaseしてから返す
  16. return [p autorelease];
  17. }
  18. else
  19. return NULL;
  20. }
  21. -(void) release {
  22. printf("%s: count = %d\n", __func__, [self retainCount]); // => 2
  23. // 内部でretain countを減らし、必要があればdeallocを呼び出す。
  24. [super release];
  25. }
  26. - (void) dealloc {
  27. printf("%s: called.\n", __func__);
  28. free(s);
  29. [super dealloc];
  30. }
  31. // copyではautoreleaseしない
  32. -(id) copy {
  33. MyStr* new_obj = [[MyStr alloc] init];
  34. if (new_obj) {
  35. new_obj->s = strdup(s);
  36. return new_obj;
  37. }
  38. else
  39. return NULL;
  40. }
  41. @end
  42. @interface Foo: NSObject {
  43. // インスタンス変数
  44. MyStr* instance_var;
  45. }
  46. -(void) m;
  47. @end
  48. @implementation Foo
  49. -(void) m {
  50. // retainを送り、勝手に解放されないようにする
  51. instance_var = [[MyStr stringWithCString: "hoge"] retain];
  52. printf("%s: count = %d\n", __func__, [instance_var retainCount]); // => 2
  53. }
  54. -(void) dealloc {
  55. printf("%s: called.\n", __func__);
  56. [instance_var release];
  57. [super dealloc];
  58. }
  59. @end
  60. int main() {
  61. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  62. Foo* p = [[Foo alloc] init];
  63. [p m];
  64. [pool release]; // ここでは解放されない
  65. // ここで解放される
  66. [p release];
  67. return 0;
  68. }

ちょっと長いですが、まず、allocで参照カウントが1になります。(行13)

名前がalloc*などでないメソッドでオブジェクトを生成するときは、autoreleaseします。copyでは、ルールによって、autoreleaseせずにオブジェクトを返します。

クラスFooでMyStrオブジェクトを所有します。自動プールで解放されないように、retainを送ります。これで参照カウントは2になります。

作為的ですが、自動プールを解放する(行75)と、まずMyStrのreleaseが呼び出されます。参照カウントが減らされますが、まだ0ではないので、オブジェクトは解放されません。

行78から、Fooオブジェクトを解放したときに、Fooのdeallocを通じて、MyStrオブジェクトが解放されます。

サイト内関連ページ

Objective-Cでガベージコレクション

外部リンク

Memory Management Programming Guide for Cocoa
Appleによる文書。
Cocoaでいこう! Macらしく
より詳しい解説があります。
Cocoa Club
ここにも、メモリ管理の解説があります。