イベントを集中管理する

JavaScriptの親戚であるActionScriptは、ご存知の通りイベント駆動型のプログラミングモデルを採用している。
ライブラリを使うのは、リファレンスを見れば分かるのだが、アプリケーションをイベント駆動で組むというのは、中々ピンときていなかった。
メソッドを呼び合うプログラムを書くことに慣れているからね。
最近やっとイベント駆動のプログラムの仕方について要点が掴めてきた。
少しずつ設計の勘所を徐々に書き残しておこうと思う。

まず、イベント駆動型のプログラミングで困るのは、イベントリスナーの外し忘れだった。
これをちゃんとしないとメモリリークが、いとも簡単に起きる。
弱参照という仕組みも用意されているのだが、これは使ってはいけない。
極めて特殊なケースで使うべき機能なので、普通は忘れちゃってもいいくらいだ。
弱参照を使うと外れて欲しくないタイミングで、ガベージコレクションされてしまうので、不可思議なバグとして負荷が掛かったときだけ発症する。

まず、俺が用意したのは、EventSweepというクラスだった。
基本的なアイディアは、イベントリスナーを記憶しておいて、一気に削除できるようにするということだけなんだけど。
外し忘れ防止には、かなり役に立つ。

EventSweep.as

package
{
    import flash.events.IEventDispatcher;

    public class EventSweep
    {
        /** イベントリスト */
        protected var m_aEventListener:Vector.<EventListener> = new Vector.<EventListener>();

        /**
         * イベントリスナーの登録
         * @param target リスナーを登録するターゲット
         * @param type イベントタイプ
         * @param func 登録するメソッド
         * @param useCapture リスナーが、キャプチャ段階、またはターゲットおよびバブリング段階で動作するかどうかを判断します。
         * @param priority イベントリスナーの優先度レベルです。
         * @param useWeakReference リスナーへの参照が強参照と弱参照のいずれであるかを判断します。
         */
        public function entryEventListener(target:IEventDispatcher, type:String, func:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void {
            target.addEventListener(type, func, useCapture, priority, useWeakReference);
            
            m_aEventListener.push(new EventListener(target, type, func, useCapture));
        }
        /**
         * 登録されているイベントリスナーを全て廃棄する
         */
        public function leaveEventListener():void {
            for each(var listener:EventListener in m_aEventListener) {
                listener.target.removeEventListener(listener.type, listener.func, listener.useCapture);
            }
            m_aEventListener.length = 0;
        }
        /**
         * 指定のイベントリスナーを削除する
         * @param   target
         * @param   type
         * @param   func
         * @param   useCapture
         */
        public function purgeEventListener(target:IEventDispatcher, type:String, func:Function, useCapture:Boolean = false):void {
            var index:int = 0;

            for each(var listener:EventListener in m_aEventListener) {
                if (listener.target == target && listener.type == type && listener.func == func && listener.useCapture == useCapture) {
                    target.removeEventListener(type, func, useCapture);
                    m_aEventListener.slice(index, 1);
                    break;
                }
                index++;
            }
        }
    }
}
{
    import flash.events.IEventDispatcher;

    /**
     * イベントリスナーを配列に格納するための値クラス
     * インナークラス
     */
    class EventListener {
        private var m_target:IEventDispatcher;
        private var m_type:String;
        private var m_func:Function;
        private var m_useCapture:Boolean;
		
        public function EventListener(target:IEventDispatcher, type:String, func:Function, useCapture:Boolean):void {
            m_target	 = target;
            m_type	 = type;
            m_func	 = func;
            m_useCapture = useCapture;
        }
        public function set target(value:IEventDispatcher):void {
            m_target = value;
        }
        public function get target():IEventDispatcher {
            return m_target;
        }
        public function set type(value:String):void {
            m_type = value;
        }
        public function get type():String {
            return m_type;
        }
        public function set func(value:Function):void {
            m_func = value;
        }
        public function get func():Function {
            return m_func;
        }
        public function set useCapture(value:Boolean):void {
            m_useCapture = value;
        }
        public function get useCapture():Boolean {
            return m_useCapture;
        }
    }
}

使い方

    var sweeper:EventSweep = new EventSweep();
			
    sweeper.entryEventListener(child1, MouseEvent.CLICK, mouseClickHandle_1);
    sweeper.entryEventListener(child2, MouseEvent.CLICK, mouseClickHandle_1);


削除方法

    sweeper.leaveEventListener();