Cocoaのメモリ管理(3)
保持と解除という方法は、理屈は分かるしそうにえます。しかし、にやってみると意外としいことがわかります。そこでCocoaでは少しをするための仕みを入しています。に言えば、とりあえずなんでも入れておけるごみ袋を用意して、不要になった点でごみ袋ごとてちゃうという方法です。このごみ袋にあたるのが、NSAutoreleasePoolというクラスです。
Application KitにおけるNSAutoreleasePool
さて、Cocoaの重要なフレムワクの一つであるApplication Kitのから始めたいと思います。Application Kitは、主にGUIを持つアプリケションを作成するためのフレムワクです。このフレムワクを利用して作ったアプリケション(つまり、ぶっちゃけた、ProjectBuilderとInterfaceBuilderを使って作られたNSApplicationを利用するアプリケション) には、デフォルトで一つNSAutoreleasePoolオブジェクトが生成されています。さらに、NSApplicationが提供するイベントルプ内でも、NSAutoreleasePoolオブジェクトが生成され、ルプが一回するごとに解放され、新しいNSAutoreleasePoolオブジェクトを生成します。つまり、NSAutoreleasePoolそのものを知らなくてもとりあえず使える仕みが提供されているわけです。このごみ袋にオブジェクトを入れる方法が?NSObjectに用意されているautoreleaseメソッドです。Foundation KitとApplication Kitが提供するクラスは、基本的にNSAutoreleasePoolがある境での作を想定しています。
- (id)autorelease
Application Kitにしたプログラムの合、任意のオブジェクトにautoreleaseを送っておくと、このオブジェクトはデフォルトの NSAutoreleasePoolオブジェクトに登されます。そして、このプログラムが了する直前にデフォルトの NSAutoreleasePoolオブジェクトとともに、登されたオブジェクトがすべて解放されます。つまりautoreleaseは、retain & releaseの仕みを使わないで、々とメモリ管理を行う仕みなのです。
- (void)methodA
{
id obj = [[Foo alloc] init];
// objとして参照されているオブジェクトをNSAutoreleasePoolに登する。
[obj autorelease];
// このメソッドをけると、数objによる参照はなくなるが、
// 割り当てられたメモリは、イベントルプのNSAutoreleasePoolが
// 解放されるに解放される。
}
NSAutoreleasePoolの特
ApplicationKitが提供するNSAutoreleasePoolを使いこなすにあたって、いくつかの注意すべき特があります。
* デフォルトのNSAutoreleasePoolは、プログラムの最後に(つまり、メインスレッドの末端で)解放されるので、基本的にこれに登されたオブジェクトに割り当てられたメモリはプログラムの最後まで解放されない。
* イベントルプ内で回生成されるNSAutoreleasePoolは、ルプが一回するごとに解放されるので、基本的にこれに登されたオブジェクトに割り当てられたメモリはイベント理がわると解放される。
* autoreleaseされたオブジェクトが、基本的にそのオブジェクトが登されたNSAutoreleasePoolがreleaseされるまで解放されないということは、逆に言えば、NSAutoreleasePoolが生きているは、オブジェクトの生存が保障されるということを意味している。
* ただし、releaseメソッドを送ることで、このようなオブジェクトでもプログラムの途中で解放することは可能である。(これは、推されない方法である。基本的に、同じオブジェクトにしてautoreleaseとreleaseを用することは混乱の元になるので、止めるべきである。 deallocについてはいわずもがな。オブジェクトのプログラマブルな解放のためには、後述する方法を用いるべきである。)
* 例外生には、NSAutoreleasePool(と、登されたオブジェクト)は解放されるので、メモリリクは起こらない。(どっちにしてもプログラムが了するので、メモリは解放される。)
* デフォルトのNSAutoreleasePoolだけで作するようなプログラム合、参照が切れたオブジェクトは、基本的にメモリリクと同じ状になる。(あえて言うなら、知った上でのメモリリク。通常のプログラムではあまりにする必要がない。)
* NSAutoreleasePoolに登されているオブジェクトへのアクセス手段はないので、参照の管理には注意が必要である。
自前のNSAutoreleasePoolの利用
さて、々と引き伸ばしてきたに入ろうと思います。まず、Application Kitが用意しているNSAutoreleasePoolだけでは、一度のイベント理で大量にオブジェクトを生成するような理にするのがしい合があります。また、そもそもApplication Kitを利用しないプログラム(たとえば、コマンドラインで作するツルなど)は、この仕みが提供されていないわけです。そこで、自前の NSAutoreleasePoolを作成して利用する意味が出てくるのです。ここであれこれいうより、まずはサンプルコドで明しましょう。以下のコド内のobj2をてわかるように、はautoreleaseメソッドを呼び出すと、スレッド内で一番最近作られた NSAutoreleasePoolにオブジェクトが登されます。つまり、テンポラリなオブジェクトは、このように自前の NSAutoreleasePoolオブジェクトを宜用意してやれば、任意の点でまとめて解放できるのです。
- (void)methodA
{
id obj1;
id obj2;
id arp;
obj1 = [[Foo alloc] init];
obj2 = [[Foo alloc] init];
[obj1 autorelease]; // obj1は、デフォルトのNSAutoreleasePoolに登される。
arp = [[NSAutoreleasePool alloc] init];
[obj2 autorelease]; // obj2は、arpに登される。
[arp release]; //arp解放。この点でobj2は、解放される。
// obj1は、上位のNSAutoreleasePoolがreleaseされるまで解放されない。
}
理解を深めるためにもう一つ例を示しておきます。
- (void)methodA
{
id temp;
id arp;
int i;
for (i = 0; i < 10; i++) {
arp = [[NSAutoreleasePool alloc] init];
temp = [[[Foo alloc] init] autorelease];
// tempで在参照されているオブジェクトは、
// arpに登される。
[arp release]; // arp解放。
// この点でtempで参照されるオブジェクトは解放される。
}
// この点で10のオブジェクトが解放されています。
}
入れ子になったNSAutoreleasePoolの利用
もうお分かりかと思いますが、NSAutoreleasePoolはいくらでも入れ子することができるのです。スレッド上で一番最近作られた NSAutoreleasePoolオブジェクトがカレントのNSAutoreleasePoolオブジェクトとして能するわけです。さて、まずは自前のNSAutoreleasePoolを活用する前に、いくつかの注意点を示しておきます。
* NSAutoreleasePoolオブジェクトにはretainを送ってはならない。
* NSAutoreleasePoolオブジェクトにはautoreleaseを送ってはならない。
* 用なバグと混乱を避けるために、NSAutoreleasePoolオブジェクトの生成と解放は、同じ文上で行うべきである。(たとえば、前述の例のように、ルプ内の理の前後など)
* retain回数 + 1 = autorelease回数がプログラム全体で成り立つことをする。
さて、スレッドの流れを把握しているなら、NSAutoreleasePoolオブジェクトを数のメソッドで使うことが可能です。 Application KitのNSAutoreleasePoolオブジェクトも基本的にこの方法の例に他なりません。まずはなサンプルを示します。この例では、 obj2がそれにあたります。
- (void)methodA
{
id obj1;
id arp1;
arp1 = [[NSAutoreleasePool alloc] init];
obj1 = [[[Foo alloc] init] autorelease]; // obj1は、arp1に登される。
[self methodB];
[arp1 release]; //arp解放。この点でobj1、obj2は、解放される。
}
- (void)methodB
{
id obj2;
id obj3;
id arp2;
obj2 = [[Foo alloc] init];
obj3 = [[Foo alloc] init];
[obj2 autorelease]; // obj2は、呼出し元のarp1に登される。
arp2 = [[NSAutoreleasePool alloc] init];
[obj3 autorelease]; // obj3は、arp2に登される。
[arp2 release]; //arp2解放。この点でobj3は、解放される。
// obj2は、arp1解放まで解放されない。
}
いままではな例でしたが、最後に少しな例を示します。それは、メソッドにりがある合のいについてです。以下のサンプルをて下さい。このコドには、重要な目がされています。つまり、NSAutoreleasePoolオブジェクトは、登されているオブジェクトにする登回数を管理していて、自身が解放されるに、その登回数分のreleaseを各オブジェクトに送っているだけだということです。つまり登回数がそのオブジェクトの保持数より少なければ、NSAutoreleasePoolオブジェクトが解放された後でも、そのオブジェクトは生きけます。この例では、retにretainを送ることでretの寿命を引き伸ばしています。
- (void)methodA
{
id obj;
id arp1;
arp1 = [[NSAutoreleasePool alloc] init];
obj = [self methodB:1];
[arp1 release]; // arp1解放。この点でobjすなわちretは、解放される。
}
- (id)methodB:(int)i
{
id ret;
id arp2;
arp2 = [[NSAutoreleasePool alloc] init];
ret = [[Foo alloc] init]; // retの保持数は1
[ret autorelease];
if (i == 1) {
[ret retain]; // retの保持数は2になる。
// autoreleaseではなく、retainを呼ぶことで、
// arp2の寿命を超えて、arp1(もしくは、
// もっと外)の文まで、retで参照されるオブジェクトの
// 寿命を延ばすことができる。
} else {
ret = nil;
}
[arp2 release]; // 判定で真だったら、この点でretの保持数は1になる。
// 判定でだったら、この点でretは解放される。
return [ret autorelease]; // retを生成したので、autoreleaseを送ってから返す。
}
autoreleaseか、retain/releaseか
autoreleaseか、retain/releaseかというですが、今までてきたようにそんなに大差はありません。状にじて、、または用するとよいでしょう。比的小さなあまりオブジェクトを作らないプログラムでは、Application KitのNSAutoreleasePoolオブジェクトを使うことで、autoreleaseだけでプログラミングするのもよいでしょう。しかし、自前のNSAutoreleasePoolオブジェクトを使うような局面なら、retain/releaseでも同じようなものでしょう。ただ、 NSAutoreleasePoolオブジェクトを使うことでソスコドのカスタマイズが容易になるという利点はあるかも知れません。なぜなら、オブジェクトは、NSAutoreleasePoolオブジェクトが少なくとも解放されるまで有なのですから。
retain/release/autoreleaseの用方
以下に垣内さん、白山さん、高さんから教えていただいた用方をまとめたものを示します。
* 基本としてオブジェクトのオナシップを意する。すなわち、以下のを守する。
【1】自分で生成したオブジェクトは、自分で解放する
【2】他人が生成したオブジェクトは、に留めない
【3】他人が生成したオブジェクトが必要なら、必ず保持(retain)して、必要にならなくなった点で、必ず解除(release or autorelease)する
* 可能な限り alloc-init系の生成メソッドは使わない。【2からの派生】
* alloc、init…を使う合は必ず autoreleaseを入れる。【1】
aFoo = [[[Foo alloc] init] autorelease];
* alloc-init系以外の生成用クラスメソッドの合は何もしない。それらのクラスメソッドは内部でalloc-init系の生成メソッドを呼んでいるため、1が用されていると考えるべきである。【2】
aFoo = [Foo foo];
* インスタンス数?グロバル数?スタティック数に代入して、参照を残す合は、retainをかける。【3】
globalFoo = [aFoo retain];
* インスタンス数?グロバル数?スタティック数のを消す合には、かならずreleaseかautoreleaseを入れる。【3】
//この例ではinstanceFooは、このクラスのインスタンス数と定する。
- (void )setFoo:newFoo
{
[instanceFoo release];
instanceFoo = [newFoo retain];
return;
}
- (void )dealloc
{
[instanceFoo release];
[super dealloc];
return;
}
* 自分でクラスを作るときは、NSStringの+string... NSArrayの+array... のようなそのクラス用の生成用クラスメソッドを作成して利用させる。【2の用】
// 生成用の引数がない合
+ foo
{
return [[[Foo alloc] init] autorelease];
}
// 生成用の引数が必要な合
+ fooWithBar: bar
{
return [[[Foo alloc] initWithBar:bar] autorelease];
}
* 必ずルプ内部でNSAutoreleasePoolを用する。
for (i = 0; i < 10; i++) {
arp = [[NSAutoreleasePool alloc] init];
temp = [[[Foo alloc] init] autorelease];
[arp release];
}