ガベージコレクションの話を続けよう。
前回の記事で、Objective-C 2.0のガベージコレクションの特徴を一言で言い表してみた。もう一度書いておこう。
Objective-Cのガベージコレクションは、保守的であり、マーク・アンド・スイープでコレクションを行い、コピーは行わず(ゆえにコンパクションも行わない)、世代別コレクションをサポートする
と、なる。今回はこの文のうち、「マーク・アンド・スイープ」について説明しよう。
マーク・アンド・スイープとは
ガベージコレクションは不必要なメモリを自動的に解放してくれる仕組みだが、そのためには、どのメモリ領域が必要で、どこが不必要(つまりゴミ)であるかを判断し、それらのゴミを回収しなくてはいけない。このゴミ集めのアルゴリズムが、ガベージコレクションの性能を決めることになる。
Objective-C 2.0で採用されているアルゴリズムは、マーク・アンド・スイープと呼ばれるものだ。このアルゴリズムを説明しよう。
まずはじめに、確実に必要なオブジェクトを知っておく必要がある。たとえば、グローバル領域に確保されているオブジェクトや、現在のスタック上にあるオブジェクトなどだ。これらを「ルートオブジェクト」と呼ぶ。ルートオブジェクトには、"これは必要だ"ということを表す印を付けておく。
次に、ルートオブジェクトが参照しているオブジェクトを探す。必要としているオブジェクトが参照しているオブジェクトも、同じように必要とされているということになる。そこで、このオブジェクトにも印を付けておく。さらにそのオブジェクトが参照しているものをたぐり、印を付ける……ということを繰り返していく。
この作業がすべて終われば、必要となっているすべてのオブジェクトには印が付いているはずだ。つまり、逆に考えれば、印が付いていないものは不必要ということである。そこで、印がついていないオブジェクトをすべて解放する。これででき上がりだ。
印(マーク)を付けて、破棄(スイープ)する、これが"マーク・アンド・スイープ"アルゴリズムだ。
Cocoaでのルートオブジェクト
これを、Cocoaアプリケーションの実地で考えてみよう。
まず、どのようなオブジェクトがルートオブジェクトになるだろう。真っ先に思いつくのは、NSApplicationのインスタンスだ。NSAppとして参照可能なインスタンスである。このインスタンスはCocoaアプリケーション自体を表し、多くのインスタンスがここから参照されることになっている。
また、各種のシングルトンタイプのオブジェクトも、グローバル領域に置かれるので、ルートオブジェクトになるだろう。これには、NSFileManagerやNSWorkspaceなどがある。
気を付けたいのは、nibファイルだ。nibファイルでは、ルート領域に好きなオブジェクトをインスタンス化できる。だが、これらはそのままではどこからも参照されていない。従ってnibファイルを読み込んだときにインスタンス化されるものの、すぐにガベージコレクションの対象となってしまう。
これを防ぐために、File's Ownerなどからアウトレットとして必ず接続しておくようにしよう。
強い参照と弱い参照
マーク・アンド・スイープの方式では、参照するだけでオブジェクトがガベージコレクションの対象ではなくなる。だが場合によっては、そうしたくないときもあるだろう。
そのためにObjective-C 2.0では、2種類の参照が用意されている。強い(strong)参照と、弱い(weak)参照だ。これらを指定するために、文法が拡張されている。
参照の種類は、インスタンス変数を宣言するときに、修飾子として指定する。強い参照を使いたいときは、__strongを指定する。弱い参照は、__weakだ。
@interface SampleClass : NSObject
{
__strong NSString* name;
__strong int* intPtr;
__weak id delegate;
}
@end
上の例では、nameとintPtrが強い参照、delegateが弱い参照となっている。参照の修飾子は、Cocoaのオブジェクトだけではなく、int型やvoid型のポインタにも使える。
__strongも__weakも指定されていない場合は、Cocoaのオブジェクトであれば、暗黙的に強い参照になる。それ以外のメモリ領域の場合は、そうはならないようだ。mallocで確保した領域や、Core Foundationのオブジェクト(C言語で書かれている)を強く参照したい場合は、明示的に__strongを指定しておく必要がある。
古いObjective-Cに慣れている方向けに説明すれば、強い参照にするか弱い参照にするかという選択は、インスタンス変数をretainで保持するかしないか、という考え方に近いものがあるだろう。つまり、強く参照しているものは自分が明示的に手放さない限り解放されないが、弱く参照しているものは解放される可能性がある。
では、弱い参照をしているオブジェクトが解放されたとしよう。このとき、解放される直前に、そのインスタンス変数にnilが設定される。これにより、すでに解放済みのオブジェクトに誤ってアクセスしてしまいクラッシュする、という潜在するバグを回避することができるのだ。これはObjective-C 1.0から比べると、非常に嬉しい追加機能と言えるだろう。