Cocoaでいこう! Macらしく 第13回
Yoshiki(DreamField)
この記事は、MOSAが発行するデベロッパ向けのデジタルマガジンMOSADeN 第72号(2003年7月1日発行)に掲載された記事です。2〜3ヶ月遅れで、ここに掲載して行きます。

autorelease poolの説明に随分と時間をかけています。CocoaでGUIを組む時には、特別な理由が無い限り生成する必要が無いautorelease pool。それなのに、これだけ詳しく説明しているのには、理由があります。実はその特別な理由は、組むプログラムによっては、良く発生することでもあるからです。

autoreleaseは入れ子にできる

autoreleaseしたものは、たとえ不要になってもイベントループに戻るまでは保持されます。これが大した数で無い時には問題になりませんが、例えばそれが何千回もループする処理で、ループの度に何十ものオブジェクトを一時的に生成する必要がある場合は、どうでしょう。それだけの数になりますと、さすがに無視できなくなります。下手をすればメモリがパンクしますし、そこまで行かなくても処理速度の低下を招くでしょう。その他の例として、ループの回数はさほどでは無くても、毎回一時的に生成するオブジェクトが、莫大にメモリを消費する巨大な物だったとしたら、どうでしょう。この場合も、なるべく早目に解放しないと、痛い目を見ることになります。

でも、autorelease poolはイベントループで既に使われています。こういう場合は、どうしたら良いでしょうか。実を言えば、autorelease poolは入れ子に出来ます。従いまして、ループの度にautorelease poolを生成し、解放すれば解決します(fig.01)。

autorelease poolは入れ子にできる
[fig.01] autorelease poolは入れ子にできる

fig01の例では、自分でallocしていますから、autoreleaseを使わなくてもreleaseできてしまいますが、これは説明のための例ですから、そこは無視してください。この例ではループのたびにautorelease poolを生成し、解放しています。これが無いと、1回のループで生成する一時的なオブジェクトの数×ループの回数だけのオブジェクトが溜まっていってしまいますが、こうすることにより、それを防ぐことができます。ここで、autorelease poolに登録する時には、autoreleaseメッセージを送るだけで、どのautorelease poolに登録するのかを指定しなかったことを思い出して下さい。autoreleaseメッセージを送りますと、一番最近生成したautorelease poolに自動的に登録されます。従いまして、fig.01の様な入れ子構造が実現できるわけです。

autoreleaseの回数

ここで、あるオブジェクトにautoreleaseメッセージを複数回送ったら、どうなるのかを書いておきます。これについては、もっと早く説明すべきでしたが、話の流れから説明できていませんでした。この場合はautorelease poolが解放された時、releaseメッセージはautoreleaseメッセージを送った回数だけ送られます。従いまして、autoreleaseメッセージを送る時は、releaseメッセージ同様、余分に送ってはいけません。

autorelease pool自身にautoreleaseは送れるのか

もう一つ、autorelease poolについて説明しておかなければならないことがあります。前回releaseは全てautoreleaseに置き換えても理論上は正しく動作すると言ったことを覚えていますでしょうか。実はこれには例外があります。fig.01を見て下さい。ここで、autorelease poolであるpool1やpool2に対し、releaseの代わりにautoreleaseメッセージを送ってしまったら、どうなるでしょうか。pool1やpool2は自分自身に登録され、自分がreleaseされる時に自分にreleaseメッセージを送る?ということは、自分にreleaseメッセージを送るためには、自分がreleaseされないといけない?まるで禅問答ですね。従いまして、autorelease pool自身は、autoreleaseできないのです。

規約の例外

retain、releaseには明確な規約があると第10回で書きました。それは次の通りでした。

  1. 最初から所有している(リファレンスカウンタが1になっている。)オブジェクトは、allocで生成した時と、copyで複製した時だけです。
  2. 1.以外で得たオブジェクトは、継続的に必要であるなら、retainメッセージを送って明確に所有しなければなりません。
  3. 所有したオブジェクトは、必要が無くなったら、自分でreleaseメッセージを送って解放しなければなりません。

これには例外があります。例えば、autorelease poolの実装もそうです。厳密に言えば3.に従っているとは言えません。ですが、これは上記規約を守れるようにするために、フレームワーク内で閉じている仕組みですから、それを気にする必要は無いでしょう。

今まで組んできたTinyViewの中にも例外があります。MyDocument.hを開いてみて下さい。この部分は、第10回で説明していますが、次のインスタンス変数が宣言されています。

IBOutlet id imageView;

nibファイルをロードすると、ここには自動的にプロジェクトビルダで指定したオブジェクトのアドレスが入ります。これは自分で生成したわけではありませんが、フレームワークが管理していますので、retainする必要はありません。

それから、まだここでは説明していませんが、デリゲートの登録、NSNotificationのオブザーバの登録、NSTableViewのデータソースの登録等、メッセージを送って欲しい先を登録するような場合は、登録した先で、その登録したオブジェクトのretainは通常行いません。ですから、登録されている間は、そのオブジェクトを勝手に消してはいけないことになります。何でこうなっているのか、私も正確な所は知らないのですが、物としてオブジェクトを渡すのではなく、相互に影響しあう処理であること。下手にretainしてしまうと、デッドロックを起こしかねないこと。この様な理由からではないかと推測しています。

※詳しくご存じの方がおられましたら、お教え下さい。よろしくお願いします。

さて、これでretain、release、autoreleaseの説明はとりあえず終わりです。Objective-Cは、クラスの生成や解放に関して自分でコントロールすることが出来ますから、効率の良いプログラムを組むことが容易です。しかも規約に従ってさえいれば、これの取り扱いも簡単でプログラムも単純になります。是非、リファレンスカウンタを使いこなし、メモリリークの無い美しいプログラムを書いて下さい。それでは、次回からは再びTinyViewに手を入れていきます。

前頁目次次頁