GADGET FACTORY 雑記帳

iPhone アプリ / Flash コンテンツ /Adobe AIR などを開発していて気づいたことや備忘録、TIPS、HACKなど

【ActionScript】Event.REMOVED_FROM_STAGEは便利だけど注意が必要かも

FlashActionScript で、ボタンなどに addEventListener を使ってイベントを設定した後、イベントを removeEventListener を使って削除しないと、どんどんとメモリを消費していきます。

このイベントの削除し忘れがよく発生するので、私がよく利用しているのが、画面遷移などでそのMovieClip が消える時にトリガーされる Event.REMOVED_FROM_STAGE を利用する方法。




例えば

  • サンプル1
btn.addEventListener(MouseEvent.CLICK,hogehoge);
btn.addEventListener(Event.REMOVED_FROM_STAGE,removeEventHandler);

...
function hogehoge(event:MouseEvent):void{
 //ボタンイベントが入ります
...
}

function removeEventHandler(event:Event):void{
 var mc:MovieClip = MovieClip(event.currentTarget);
 mc.removeEventListener(Event.REMOVED_FROM_STAGE,removeEvent);
 switch(mc.name){
  case "btn":
   mc.removeEventListener(MouseEvent.CLICK,hogehoge);
   break;
 }
}

Event.REMOVED_FROM_STAGE は stage 上からターゲットとなる MovieClip が除外される際にトリガーされます。

従いまして、タイムラインでステージ上にあらかじめ配置している MovieClip でも、addChild で追加された MovieClip でも、そのイベントが付加されたターゲットが消えるタイミングで、処理を実行することができるので、追加したイベントの削除忘れが防げるので便利。

さて、ここからが本題。

MovieClip が削除される時にトリガーがかかるということは、removeChild で削除しない限りは、画面遷移の時にトリガーがかかるということ。
すなわち、画面切り替え時に処理したい項目を、ここで記載しておけば良いのでは?
ということを考えて、上記の removeEventHandler の中に、遷移時の処理を併記してしまったのです。

例えば、他の MovieClip に追加したイベントを削除するとか、初期化するとか、そういうたぐいの物。

先ほどのコードに例として追加すると、以下のようなもの

  • サンプル2
btn.addEventListener(MouseEvent.CLICK,hogehoge);
btn.addEventListener(Event.REMOVED_FROM_STAGE,removeEventHandler);

btn2.addEventListener(MouseEvent.CLICK,fugofugo);

...
function hogehoge(event:MouseEvent):void{
 //ボタンイベントが入ります
...
}

function fugofugo(event:MouseEvent):void{
 //ボタンイベントが入ります
...
}

function removeEventHandler(event:Event):void{
 var mc:MovieClip = MovieClip(event.currentTarget);
 mc.removeEventListener(Event.REMOVED_FROM_STAGE,removeEvent);
 switch(mc.name){
  case "btn":
   mc.removeEventListener(MouseEvent.CLICK,hogehoge);
   break;
 }

 if(btn2){
  if(btn2.hasEventListener(MouseEvent.CLICK)){
   btn2.removeEventListener(MouseEvent.CLICK,fugofugo);
  }
 }

}

「btn2」という MovieClip に追加した 「fugofugo」というイベントを、「btn」が消える時に合わせて削除するという意図です。

一見すると、サンプル2のコードで動作するようにも見えます
ところが、このコードでは、成功することもあれば、失敗することもあります。

その理由は、MovieClip が削除される順番によって、成功/失敗が変化するためです。

サンプル2の場合、Event.REMOVED_FROM_STAGE イベントを付加した「btn」という MovieClip が、「btn2」よりも先にステージから消える場合は、「btn」に付加している「removeEventHandler」が呼び出されるので、意図した通りの動作として、「btn」「btn2」のイベントを全て削除することができます。

しかし、「btn2」が先にステージから消え、続いて「btn」がステージから消える場合は、「btn2」が消える時には上記の「removeEventHandler」は呼び出されることがないため、「btn2」のイベントは残ったままとなります。

この部分を横着してしまったため、キッチリと想定通りの動作をしなくて、その原因を調べるのに少し時間がかかってしまいました。

上記のコードをちゃんと動作するものにするならば、

  • サンプル3
btn.addEventListener(MouseEvent.CLICK,hogehoge);
btn.addEventListener(Event.REMOVED_FROM_STAGE,removeEventHandler);

btn2.addEventListener(MouseEvent.CLICK,fugofugo);
btn2.addEventListener(Event.REMOVED_FROM_STAGE,removeEventHandler);

...
function hogehoge(event:MouseEvent):void{
 //ボタンイベントが入ります
...
}

function fugofugo(event:MouseEvent):void{
 //ボタンイベントが入ります
...
}

function removeEventHandler(event:Event):void{
 var mc:MovieClip = MovieClip(event.currentTarget);
 mc.removeEventListener(Event.REMOVED_FROM_STAGE,removeEvent);
 switch(mc.name){
  case "btn":
   mc.removeEventListener(MouseEvent.CLICK,hogehoge);
   break;
  case "btn2":
   mc.removeEventListener(MouseEvent.CLICK,fugofugo);
   break;
 }
}

とすれば、きっちと動作します。

サンプル2よりもサンプル3の方が、コード的には短いのに、なぜ私はサンプル2のようなコードを書いたのか?
それは、MovieClip に MouseEvent 以外の処理を加えていたからです。

上記のサンプルでは、分かりやすくするために、MovieClip「btn」「btn2」に MouseEvent を追加するコードにしましたが、実際には、「btn2」 の MouseEvent に該当する部分に、TimerEvent だったり、Event.ENTER_FRAME のイベントだったりを記載していました。

それらのイベントを追記する都度、 Event.REMOVED_FROM_STAGE を追記するのが面倒だったので、上記のように、MovieClip の1つに代表で追加して、処理をするようなコードを書いていました。

結論としては、横着してはダメだということですね。

イベントリスナーの追加、削除については、他にもっと良い書き方がないかと、常々思っていますので、思いついたり、発見したらブログにてまた改めて書きたいと思います。