Haxe + CreateJS 開発メモ#5 removeEventListener が機能しない問題の回避方法

以前取り上げましたが、EaselJS v0.6.0 からイベントモデルが変更されています。

EaselJS v0.6.0で新しくなったイベントモデル

EventDispatcher クラスが実装されて、addEventListener や removeEventListener、dispatchEvent 等のメソッドが使えるようになりました。 しかし、Haxe で実装した場合、removeEventListener が正しく動かない場合があるようです。なかなか致命的です。

サンプル

package ;

import createjs.easeljs.EventDispatcher;
import haxe.Log;

class RemoveEventListenerTest extends EventDispatcher {

	public function new() {
		addEventListener("test", testHandler);
		dispatchEvent("test");
		dispatchEvent("test");
	}

	private function testHandler(e:Dynamic):Void {
		removeEventListener(e.type, testHandler);
		Log.trace(e);
	}
	
}

出力結果

RemoveEventListenerTest.hx:16: {
	type : test, 
	target : [EventDispatcher]
} Boot.hx:45
RemoveEventListenerTest.hx:16: {
	type : test, 
	target : [EventDispatcher]
} Boot.hx:45

上記の例では、最初にイベントが送出された時点で removeEventListener しているはずなのですが、ログが二度出力されてしまいます。

この問題を調査した所、addEventListener や removeEventListener に渡される関数がクラスの prototype にある場合に、Haxe コンパイラによってクロージャでラップされてしまう事が原因のようです。このため、EventDispatcher の持つリスナーオブジェクト内の関数との比較が一致せず、イベントリスナの除去が正しく行われません。

createjs-haxe-eventdispatcher

幸い、元の関数とコンテキストへの参照がアクセス可能なので、これらの値を利用して、Haxe でも正しく動作する removeEventListener メソッドを作ってみます。

(function() { var p = createjs.EventDispatcher.prototype; p.removeEventListener = function(type, listener) { var listeners = this._listeners; if (!listeners) { return; } var arr = listeners[type]; if (!arr) { return; } for (var i=0,l=arr.length; iCreateJS のライブラリ群を読み込んだ後に、このように createjs.EventDispatcher.prototype.removeEventListener を上書きする処理を実行する事で、この問題を回避する事に成功しました。
※2013/06/11追記
また、MouseEvent 等のクラスは、実際には EventDispatcher を直接継承していないので、個別にメソッドを上書きする必要があります。