サイトトップ

Director Flash 書籍 業務内容 プロフィール

Macromedia Flash非公式テクニカルノート

予め配置したMovieClipのリスナーがインスタンスのないフレームに移動してもイベントを受取る

ID: FN1011002 Product: Flash CS5 and above Platform: All Version: 9 and above/ActionScript 3.0

問題
予めタイムラインに置いたMovieClipインスタンスに登録したイベントリスナーが、インスタンスのないフレームに移動してもイベントを受取ることがあります。Flash Player 9以降/ActionScript 3.0で見られる現象です。

たとえば、メインタイムラインに2フレームをつくり、その第1フレームのみにMovieClipインスタンスを置きます(図001)。MovieClipインスタンスには、とくにビジュアルエレメントは要りません。

図001■全2フレームのメインタイムライン第1フレームにMovieClipインスタンスを置く
図001左図 図001右図

MovieClipシンボルの第1フレームに、以下のスクリプト001を記述します(図002)。インスタンスには、DisplayObject.enterFrameイベント(定数Event.ENTER_FRAME)のリスナー関数(xTest())を登録します。

メインタイムラインの第2フレームにはMovieClipインスタンスがありませんので、第1フレームに戻るたびにインスタンスは新たに認識されます。そこで、インスタンスを識別するため、変数(id)にgetTimer()関数が返す整数を設定しています。そして、リスナー関数は、Event.typeプロパティのイベント名"enterFrame"と変数の整数値を[出力]します。

スクリプト001■MovieClipシンボルのフレームアクションでインスタンスにイベントリスナーを登録

// フレームアクション: メインタイムラインに配置したMovieClip
var id:uint = getTimer();
addEventListener(Event.ENTER_FRAME, xTest);
function xTest(eventObject:Event):void {
  trace(eventObject.type, eventObject.currentTarget.id);
}

図002■MovieClipシンボルに第1フレームアクションを記述
図002

メインタイムラインの第1フレームには、以下のスクリプト002を書きます。DisplayObject.enterFrameイベントのリスナー関数(xFrame())から、ループ再生する再生ヘッドがどのフレームにあるのかを[出力]します。

スクリプト002■ループするメインタイムラインから再生ヘッドのあるフレームを[出力]

// フレームアクション: メインタイムライン
addEventListener(Event.ENTER_FRAME, xFrame);
function xFrame(eventObject:Event):void {
  trace(this, currentFrame);
}

図003■メインタイムラインの第1フレームでDisplayObject.enterFrameイベントにリスナーを登録
図003

[ムービープレビュー]を確かめると、メインタイムラインのフレームループを繰返すたびに、MovieClipインスタンスのリスナー関数が増えていくように見えます(図004)。

図004■フレームをループするたびにMovieClipインスタンスのリスナー関数が増えていく
図004

まずメインタイムラインの第1フレームで、MovieClipインスタンスのDisplayObject.enterFrameイベントにリスナー関数が登録されます。そして、つぎの第2フレームに進むと、MovieClipインスタンスはなくなっているのに、リスナー関数が呼出されています。

さらに、第1フレームにループで戻ると、改めてMovieClipインスタンスが認識されて、イベントリスナーが登録されます。しかし、前のループのリスナーは、まだイベントを受取っています。すると、つぎの第2フレームでは、MovieClipインスタンスはないにもかかわらず、ふたつのリスナー関数がイベントを受取ります。

こうして、メインタイムラインのフレームループを繰返すたびに、イベントリスナーが増えていくようです。


原因
ActionScript 3.0では、タイムラインからインスタンスが消えることは子インスタンスの納められる表示リストから除かれることに過ぎず、インスタンスそのものがメモリから消されることを意味しません。もっとも、そのインスタンスに対する参照がすべてなくなれば、いずれ「ガベージコレクション」によりメモリは解放されます[*1]。ただし、それがいつ行われるかは決められず、ガベージコレクションをスクリプトで実行することはできません[*2]

実験としては、メモリを大きく費やすことで、ガベージコレクションを促すことはできます。たとえば、前掲スクリプト002につぎのステートメントを加えると、参照のないTextFieldインスタンスが大量につくられますので、ガベージコレクションは速やかに働きます(図005)。もちろん、メモリを浪費しているので実用には使えません。

// フレームアクション: メインタイムライン
addEventListener(Event.ENTER_FRAME, xFrame);
function xFrame(eventObject:Event):void {
  trace(this, currentFrame);
}

// フレームアクションに追加
for (var i:uint = 0; i < 10000; i++) {
  var dummy_txt:TextField = new TextField();
}

図005■メモリを費やすとガベージコレクションが促される
図005左図 図005右図

[*1]「ガーベジコレクション」(garbage collection)とは、オブジェクトへの参照を確かめ、すべての参照がなくなるとメモリを自動的に解放する仕組みです。なお、拙著『ActionScript 3.0による三次元表現ガイドブック』Column 01「ガベージコレクションと弱い参照」(PDFプレビュー公開)p.024-025およびF-site「DisplayObjectインスタンスの削除とガベージコレクション」をご参照ください。

[*2] デバッグのときにかぎり、ガベージコレクションを明示的に実行するメソッドとしてSystem.gc()があります。


対処法
たとえ目に見える問題は生じなかったとしても、タイムラインからインスタンスが消えるときにはイベントリスナーを削除しておきましょう。そうすれば、メモリやCPUを無駄に費やさずに済みます。Stageオブジェクトを頂点とする表示リストからインスタンスが外れるときには、DisplayObject.removedFromStageイベント(定数Event.REMOVED_FROM_STAGE)が起こります。そのリスナー関数で、要らなくなるイベントリスナーを削除します。

たとえば、前掲スクリプト002であれば、つぎのようにDisplayObject.removedFromStageイベントでイベントリスナーを削除します(スクリプト003)。すると、インスタンスがなくなったフレームでは、イベントリスナーが呼ばれなくなります(図006)。

スクリプト003■MovieClipシンボルのフレームアクションにイベントリスナー削除の処理を加える

// フレームアクション: メインタイムラインに配置したMovieClip
var id:uint = getTimer();
addEventListener(Event.ENTER_FRAME, xTest);
addEventListener(Event.REMOVED_FROM_STAGE, xClearListeners);   // 追加
function xTest(eventObject:Event):void {
  trace(eventObject.type, eventObject.currentTarget.id);
}
// イベントリスナー削除の関数を追加定義
function xClearListeners(eventObject:Event):void {
  trace(eventObject.type);
  removeEventListener(Event.ENTER_FRAME, xTest);
  removeEventListener(Event.REMOVED_FROM_STAGE, xClearListeners);
}

図006■インスタンスがステージから消えるときイベントリスナーを削除する
図006上図
図006下図

今回、MovieClipシンボルはテスト用に単純化して1フレームで済ませました。しかし、複数フレームをアニメーションしているときは、これもシステムのリソースを費やします[*3]。したがって、MovieClipインスタンスがタイムラインからなくなるときには、イベントリスナーの削除とともにフレーム移動を止めておくことも必要です。

[*3] Colin Moock氏は「The Official "visible vs alpha vs removeChild()" Showdown」(InsideRIA)で、「タイムラインの再生はシステムリソースを費やす」(The timeline playback will consume system resources)と述べています。


作成者: 野中文雄
作成日: 2010年11月5日


Copyright © 2001-2010 Fumio Nonaka.  All rights reserved.