不要になったイベントリスナをきちんとremoveEventListenerすることは、イベントをチューニングするうえでの基本中の基本です。なかでもEvent.ENTER_FRAMEのイベントリスナは、使用頻度も多いため、しっかりと管理する必要があります。イベントリスナの有効期限は、removeEventListenerされるか、オブジェクトもしくはリスナそのものが削除(ガベージ・コレクションなど)されるまでです。
注意しなければならないのは、「addEventListenerした対象がremoveChildされたとしても、ENTER_FRAMEイベントは通知され続ける」という点です。これらをしっかりとremoveEventListenerしておかないと、処理負荷の原因となってしまいます。
ですから、Event.ENTER_FRAMEやTimerEvent.TIMERなど、継続的に通知され続けるようなイベントに関しては、removeChildされたときや、不要になったときは必ずremoveEventListenerをする癖を付けましょう。
以下に、その実装例を示します。
addEventListener( Event.ENTER_FRAME, trace ); addEventListener( Event.REMOVED_FROM_STAGE, _onRemoved ); // ステージから削除された際にremoveEventListenerする function _onRemoved(e:Event){ removeEventListener( Event.ENTER_FRAME, trace ); removeEventListener( Event.REMOVED_FROM_STAGE, _onRemoved ); }
Event.REMOVED_FROM_STAGEとEvent.REMOVEDはまったく別ものです。REMOVED_FROM_STAGEは自分自身がstageからremoveChildされた際にdispatchされますが、REMOVEDは、自分自身であろうと自分にaddChildされた子要素であろうと、stageからのremoveChildであろうとなかろうとdispatchされます。
適切に削除されず残ってしまったイベントリスナは、目に見えないところでCPUにかなりの負荷を掛けます。removeEventListenerを徹底することは、サービス全体のパフォーマンスだけでなく、ユーザーのほかの作業への影響を減らすことにもつながります。
イベント関連の高速化手法で、特に描画速度に効果があるのが、リスナ関数の一括実行です。特に、Event.ENTER_FRAMEを使って大量のDisplayObjectを同時に動かす場合に効果を発揮します。
おさらいでも説明したとおり、毎フレームで何かを実行する際にはEvent.ENTER_FRAMEを使います。しかし、大量のMovieClipなどをアニメーションさせる際に、オブジェクト指向的発想でEvent.ENTER_FRAMEを個々にaddEventListenerしてしまうと、かなりの処理負荷となってしまいます。
// 各オブジェジェクトが個々にaddEventListenerするサンプルコード import flash.display.MovieClip; import flash.events.Event; class ExMovieClip extends MovieClip { public function ExMovieClip():void { addEventListener( Event.ENTER_FRAME, _onEnterFrame ); } private function _onEnterFrame(e:Event):void { x += 10; } }
このような実装自体に問題はないのですが、インスタンスが1000個など大量になってくると、「自分に登録されたリスナ関数を調べて実行する」という処理も大量に実行されてしまうのです。そのため、描画うんぬんに処理負荷が掛かるという話以前に、イベントの処理部分の時間が増加してしまい、結果としてFPSが低下してしまいます。
おのおのにENTER_FRAMEを設定した場合(Flash以外の部分をクリックすると、停止します) |
そこで、おのおのでaddEventListenerを扱うのではなく、「実行したい関数(Listener)を内部的にVector.<Function>などの配列に格納し、少数(可能であれば1つ)のイベントリスナ内でfor文などを用いて一気に実行する」というやり方にすると、イベントの処理部分が大幅に減るため、FPSがかなり改善されます。
// イベントリスナを一括実行するサンプル import flash.display.*; import flash.events.Event; class ExMovieClip extends MovieClip { // ENTER_FRAMEを共通して扱うためのSprite private static var _dispatcher:Sprite = new Sprite(); // ENTER_FRAMEを共通して扱うためのSprite private static var _listeners:Vector.<Function> = new Vector.<Function>(); public function ExMovieClip():void { // リスナ登録がない場合に登録 if( !_dispatcher.hasEventListener(Event.ENTER_FRAME) ){ _dispatcher.addEventListener(Event.ENTER_FRAME, function(e:Event):void{ var len:uint = _listeners.length; for( var i:int = 0; i < len; i++ ){ _listeners[i](e); } }) } addEventListener( Event.ENTER_FRAME, _onEnterFrame ); } // addEventListenerをoverrideし、ENTER_FRAMEの場合は_listenersに登録 override public function addEventListener( type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false ):void{ if ( type == Event.ENTER_FRAME) { _listener.push(listener); }else { super.addEventListener(type, listener, useCapture, priority, useWeakReference); } } // removeEventListenerをoverrideし、ENTER_FRAMEの場合は_listenersから削除 override public function removeEventListener( type:String, listener:Function, useCapture:Boolean = false ):void{ if ( type == Event.ENTER_FRAME) { for (var i:int = 0; i < _listener.length; i++) { if ( _listener[i] == listener ) _listener.splice(i, 1); } }else { super.removeEventListener(type, listener, useCapture); } } private function _onEnterFrame(e:Event):void { x += 10; } }
ENTER_FRAMEを一括処理した場合(Flash以外の部分をクリックすると、停止します) |
for文などで一気に処理する場合、addEventListenerのpriorityやuseCaptureの設定を加味しつつ処理させることは非常に難しいので、それらの設定を無視できるような処理で使いましょう。。
余談ですが、このイベントリスナをまとめるやり方は、TimerEvent.TIMERでも使えます。同一のイベントで、かつ大量の処理が必要なイベントに関しては、このイベントリスナの一括実行を検討するのもいいでしょう。
次ページでは、MouseEventのチューニングを解説します。
Copyright © ITmedia, Inc. All Rights Reserved.