そのオブジェクト、本当に消えていますか?

(このエントリは‘removeMovieClip()の注意点’の続きです。)

MovieCLip.removeMovieClip() を唱えると、
そのMovieClipとMovieClipの持ち物がすべて削除される、、、
そう思っていた時期が僕にもありました。

removeMovieClip()といえども、
その持ち物はガベージコレクションのメモリ管理から逃れられているわけではありません。
ガベージコレクションとはFlashが不要になったメモリ領域を自動的に解放する機能です。
(http://ja.wikipedia.org/wiki/ガベージコレクション)
参照カウントという仕組みで、不要になったオブジェクトを判定します。
不要と判断されたオブジェクトは自動的に破棄されます。
(http://ja.wikipedia.org/wiki/参照カウント)

 

例えば以下のようなコードの場合、次のようなことが起こっています。

function func():Void
{
var o:Object = new Object();
}

3行目でObjectインスタンスを生成し、 ローカル変数oに格納しています。
このとき、Objectインスタンスは変数oから’参照’されているので 参照カウントが1になります。

4行目で関数を抜けると、変数oはローカル変数なので削除されます。
これでObjectインスタンスは誰からも参照されなくなったので、
参照カウントが0になります。

どのくらいの頻度でガベージコレクションがおこなわれているのかは謎ですが、 つぎにガベージコレクションがおこなわれるタイミングで Objectインスタンスは自動的にメモリから破棄されます。
ガベージコレクションのおかげで、 過去に生成したオブジェクトが不必要になったにもかかわらず メモリを占有してしまうことをふせげるわけです。

 

さて、removeMovieClip()に話を戻しますが、 例えば以下のコードの場合、

 //空のムービークリップ'mc'を生成
var m:MovieClip = _root.createEmptyMovieClip("mc", _root.getNextHighestDepth());
//mcの持ち物としてObject'o'を生成
m.o = new Object();
//oのイベントハンドラ
m.o.onMouseDown = function ()
{
trace("I'm alive !!");
}
//oをMouseのリスナに追加
Mouse.addListener(m.o); 

上記のコードはヘルプにもよく登場する、
リスナ用のオブジェクトを生成しイベントを受け取るやつです。
ムービークリップを生成し、その持ち物としてObjectを生成しています。
Objectインスタンスは、Mouseクラスのリスナとして登録されているので、
マウスイベントを受け取ることができます。

このコードを_rootのタイムラインに記述し実行すると、
マウスをクリックするたびに’I’m alive !!’がトレースされます。

 

では、removeMovieClip()でこのムービークリップを消してみます。

//空のムービークリップ'mc'を生成
var m:MovieClip = _root.createEmptyMovieClip("mc", _root.getNextHighestDepth());
//mcの持ち物としてObject'o'を生成
m.o = new Object();
//oのイベントハンドラ
m.o.onMouseDown = function ()
{
trace("I'm alive !!");
}
//oをMouseのリスナに追加
Mouse.addListener(m.o);
//mcを削除
m.removeMovieClip();

このコードを実行し、マウスをクリックすると何が起こるでしょうか?

removeMovieClip()したのだから、その持ち物であるObjectインスタンスも破棄されてほしいところですが、 実はそううまくは行きません。
このコードを実行しても、マウスをクリックするたびに’I’m alive !!’がトレースされます。
何が起こっているのでしょうか?

参照カウントのことを思い出せば答えはわかります。
m.removeMovieClip()の時点で、mの持ち物である変数’o’は削除され、 Objectインスタンスはムービークリップ’m’からは参照されなくなっていますが、 まだ、Mouseクラスからは参照されています。 (addListener()はMouseクラスが保持している配列に、渡されたオブジェクトの参照を格納します。)
つまり、Objectインスタンスの参照カウントがまだ1であるために Objectインスタンスは破棄されず生き残っているわけです。

 

次のコードも間違いやすい例です。

//空のムービークリップ'mc'を生成
var m:MovieClip = _root.createEmptyMovieClip("mc", _root.getNextHighestDepth());
//mcの持ち物としてObject'o'を生成
m.o = new Object();
//oのイベントハンドラ
m.o.onMouseDown = function ()
{
trace("I'm alive !!");
}
//oをMouseのリスナに追加
Mouse.addListener(m.o);
//oを削除!
delete m.o;
//mcを削除
m.removeMovieClip();

Objectインスタンスを削除したいという意気込みは感じますが、
deleteステートメントはあくまでmの持ち物である変数’o’(Objectインスタンスへの参照)を削除しただけであって、Objectインスタンス自体を削除する命令ではありません。
この場合もやはり、Objectインスタンスは破棄されず生き残っています。

 

Objectインスタンスを確実に破棄したい場合は、 参照カウントを0にする必要があります。

//空のムービークリップ'mc'を生成
var m:MovieClip = _root.createEmptyMovieClip("mc", _root.getNextHighestDepth());
//mcの持ち物としてObject'o'を生成
m.o = new Object();
//oのイベントハンドラ
m.o.onMouseDown = function ()
{
trace("I'm alive !!");
}
//oをMouseのリスナに追加
Mouse.addListener(m.o);
//oをMouseのリスナから削除
Mouse.removeListener(m.o);
//mcを削除
m.removeMovieClip();

 

以上、比較的シンプルな例で説明してみましたが、 気をつけないと消えているつもりのオブジェクトがいつまでも存在するはめになります。
特に、クラス主体で開発を進めるときには注意が必要です。
参照の参照で参照されても、参照カウントはちゃんと1増えています(笑

少なくとも以下の点には注意した方がいいです。

  • addListener, addEventListenerした際は、
     必要がなくなった時点でremoveListener, removeEventListenerする

  • 不用意にメンバ変数でよそのクラスを参照しない。
     (なるべくローカル変数にその都度、参照を取り寄せるとか、クラス同士を疎結合にする設計を考える)

  • removeMovieClip()をラップしたdestroyメソッドなどを用意し、
     不必要な参照をクリアする

 

で、最後になりましたが、 エントリ’removeMovieClip()の注意点’の続きです。

var mc:MovieClip = _root.createEmptyMovieClip("emptyMC",1);
mc.removeMovieClip();
trace("*"+mc+"*");

上記のコードでトレースの内容が変だと騒いでいましたが、 考えてみれば当たり前でした。 removeMovieClip()した後も、 変数mcでMovieClipインスタンスが参照されているため、 不完全に破壊されたMovieClipインスタンスが残ってたのでした。 、、、

という理解であっていると思う。。多分。。。

カテゴリー: ActionScript2 パーマリンク

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です