addEventListenerで登録した処理が複数回実行される問題

と言うものでしばしハマったのだが、原因はTitaniumのバグではなく仕様であり、自分のプログラムのやり方だった。

原因は同じイベントに複数のコールバックを設定できるため、同じ内容でaddEventListenerを複数回実行すると同じ処理が複数回走ってしまうというもの。

ハマりついでにいろいろ検証したのでaddEventListenerとremoveEventListenerの挙動を整理してみた。

イベントに複数のコールバックを登録する

同じイベントに対してaddEventListerを複数回呼び出すと、そのイベントに対するコールバックが複数登録される。

Ti.App.addEventListener("custom_event", function(e) {
  return Ti.API.info("ONE");
});
Ti.App.addEventListener("custom_event", function(e) {
  return Ti.API.info("TW0");
});
Ti.App.addEventListener("custom_event", function(e) {
  return Ti.API.info("THREE");
});
Ti.App.fireEvent("custom_event", {});

出力は以下のようになる。

[INFO] ONE
[INFO] TWO
[INFO] THREE

上書きを期待しているとバグの原因になるので注意。

また複数のオブジェクトに内容がちょっとだけ違うコールバックをループで登録するような場合も、間違えてループが二回以上回ってしまったりすると、同じ処理が何回も行われることになる。自分がやってしまったのはこちらのケースだった。

イベントに設定したコールバックをひとつ削除

addEventListenerでコールバックを名前のついた関数で指定した場合は、removeEventListenerに同じ関数を渡すことで削除できる。

callback1 = function(e) { return Ti.API.info("ONE"); };
callback2 = function(e) { return Ti.API.info("TW0"); };
callback3 = function(e) { return Ti.API.info("THREE"); };
Ti.App.addEventListener("custom_event", callback1);
Ti.App.addEventListener("custom_event", callback2);
Ti.App.addEventListener("custom_event", callback3);
Ti.App.removeEventListener("custom_event", callback2);

Ti.App.fireEvent("custom_event", {});

出力は以下のようになる。

[INFO] ONE
[INFO] THREE

addEventListenerに無名関数を渡した場合は同じ内容の無名関数をremoveEventListenerに指定しても削除できない。

イベントに設定したコールバックをすべて削除

関数引数がなければすべてのコールバックが削除される。
という挙動をしたように思ったのだが、アプリが落ちるようだ。

Ti.App.removeEventListener("custom_event");

この状態でイベントが発火するとアプリが落ちる。
存在しないイベントを発火しても落ちない。アプリが落ちていたのは上記の第二引数にコールバックを指定してないremoveEventListenerのせいだったらしい。

コールバックをすべて削除する手段はないのかな。

参考までに

引き起こされた問題の実例も書いておく。
以下のような問題が起きていた。

  • OptionDialogが二度ボタンをクリックしないと閉じない
  • WebViewのリンクから発火させたイベントが二回実行される

OptionDialogの方は二回開かれていたというオチ。
本家のQAに同じようなケースに関する質問がいくつもあったので、そこそこハマりどころのようだ。

2011/02/06追記

removeEventListnerですべてのイベントを削除するという箇所を訂正。