(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
- #include <stdio.h>
- #include <Foundation/Foundation.h>
-
- @interface Foo: NSObject
- @end
-
- @implementation Foo
- -(void) dealloc {
- printf("%s: called.\n", __func__);
- [super dealloc];
- }
- @end
-
- int main() {
- NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
-
-
- [[[Foo alloc] init] autorelease];
- [[[Foo alloc] init] autorelease];
-
-
- printf("pool release...\n");
- [pool release];
- return 0;
- }
クラス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
- #include <stdio.h>
- #include <Foundation/Foundation.h>
-
- @interface MyStr: NSObject {
- char* s;
- }
-
- +(MyStr*) stringWithCString: (const char*) cstr;
- @end
-
- @implementation MyStr
- +(MyStr*) stringWithCString: (const char*) cstr {
- MyStr* p = [[MyStr alloc] init];
- printf("%s: count = %d\n", __func__, [p retainCount]);
- if (p) {
- p->s = strdup(cstr);
-
- return [p autorelease];
- }
- else
- return NULL;
- }
-
- -(void) release {
- printf("%s: count = %d\n", __func__, [self retainCount]);
-
- [super release];
- }
-
- - (void) dealloc {
- printf("%s: called.\n", __func__);
- free(s);
- [super dealloc];
- }
-
-
- -(id) copy {
- MyStr* new_obj = [[MyStr alloc] init];
- if (new_obj) {
- new_obj->s = strdup(s);
- return new_obj;
- }
- else
- return NULL;
- }
- @end
-
- @interface Foo: NSObject {
-
- MyStr* instance_var;
- }
- -(void) m;
- @end
-
- @implementation Foo
- -(void) m {
-
- instance_var = [[MyStr stringWithCString: "hoge"] retain];
- printf("%s: count = %d\n", __func__, [instance_var retainCount]);
- }
-
- -(void) dealloc {
- printf("%s: called.\n", __func__);
- [instance_var release];
- [super dealloc];
- }
- @end
-
- int main() {
- NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
-
- Foo* p = [[Foo alloc] init];
- [p m];
-
- [pool release];
-
-
- [p release];
- return 0;
- }
ちょっと長いですが、まず、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
- ここにも、メモリ管理の解説があります。