May 25, 2007
イベントリスナ (AS3) とガーベジコレクション
不要になったオブジェクトへの参照が残っていると、そのオブジェクトの使用しているメモリを開放することができません。特に複数の参照を持つオブジェクトに対しては、参照の消し忘れによるメモリリークが発生しないよう注意が必要です。
イベントリスナを登録すると、イベントのターゲットになるオブジェクトとイベントリスナを持つオブジェクトの間で参照ができます。AS3 では下のような記述になります。
eventTarget.addEventLisener("type", eventHandler);
このコードを実行すると eventTarget と this の間に参照がつくられます。(eventHandler は this オブジェクトのメソッド)
ところが、これは明示的な参照の追加ではありません。そのため、参照の削除が必要な場合でも見落としてしまいそうですよね。
というわけで、今回はイベントリスナ追加時の参照の扱い方についてです。
参照の方向
参照には方向性があります。つまりオブジェクト間の参照は一方向のみ可能ということです。例えば以下のようなコードがあったとします。
var foo = New Foo(); foo.bar = this; foo = null;
一行目で作られている参照は ”このオブジェクト (this)” → ”New Foo() で作られたオブジェクト” という方向の参照です。二行目で作られている参照はこの逆で ”New Foo() で作られたオブジェクト” → ”this” という方向です。三行目では一行目で作成した参照を削除しています。そのため ”this” から ”New Foo() で作られたオブジェクト” へと辿ることはもうできません。
前の記事にあるように、ガーベジコレクタはオブジェクトツリーのルートから参照を辿ります。まず、ルートの子オブジェクトを見つけて、次にその子オブジェクトの子オブジェクトを見つけるという動作を繰り返します。
とすると、上のサンプルが実行された場合、”this” が親オブジェクトであれば、ガーベジコレクタは ”New Foo() で作られたオブジェクト” を見つけることができません。つまり二行目で作成した参照は残っていたとしても、三行目が実行されていればメモリリークの心配は無いことになります。
メモリリークの原因になり得るのは、親から子への参照のみであるということですね。
子オブジェクトへのイベントリスナ追加
では、イベントリスナ追加時にはどの方向の参照ができるのでしょうか。
var foo = New Foo();
addChild(foo);
foo.addEventListener("click", clickHandler);
上の例では foo という子オブジェクトを作成して、その子オブジェクトである foo に clickHandler というイベントリスナを登録しています。clickHandler は親オブジェクトのメソッドです。
この場合には、子オブジェクトから親オブジェクトの方向の参照が作られます。 そのためイベントリスナはそのまま放置しておいてもメモリリークを引き起こすことはありません。親オブジェクトは不要になったら foo への明示的な参照を全て削除すれば十分です。
つまり、イベントリスナの追加時には、イベントリスナが追加されるオブジェクト → イベントリスナを持つオブジェクトの方向に参照が作成されているということです。(より正確には、イベントリスナが追加されるオブジェクト → イベントリスナ → イベントリスナを持つオブジェクト)
Flex ではイベントリスナの設定に以下のような記述が良く使われます。
<my:Foo id="foo" click="clickHander(event)" />
このケースでも、foo を後で削除する場合、イベントリスナが登録されているかどうかは気にしなくてもよいわけです。Flex 書いた事のある方は分かりますよね?
親オブジェクトへのイベントリスナ追加
次は下のようなケースを考えて見ます。
var foo = New Foo();
addChild(foo);
addEventListener("click", foo.clickHandler);
今度は親オブジェクトが子オブジェクトのメソッドを登録しています。ということは、親から子への参照が作成されているということです。
このような場合は、子オブジェクトへの参照を削除する際にイベントリスナも削除しないとメモリリークの原因になります。
下のように子オブジェクトから親オブジェクトにイベントリスナを設定する場合も同様です。
parent.addEventListener("click", clickHandler);
イベントリスナの削除には removeEventListener() を使います。
parent.removeEventListener("click", clickHandler);
また Flex に話を移しますが、特に Flex では親子関係が分かり難い場合があります。例えばポップアップ表示されるウインドウオブジェクトがあったとして、その中でクリックイベントのリスナを SystemManager に登録した場合、
SystemManager.addEventListener("click", clickHandler);
SystemManager からポップアップウインドウへの参照が作られます。
ところで SystemManager はポップアップウインドウを管理する親オブジェクトでもあります。そのため、SystemManager からポップアップウインドウへの参照が 1. popupChildren の配列からと、2. イベントリスナ経由、の 2 つ存在することになります。従って、この場合は、ポップアップウインドウを削除する時にイベントリスナも削除しないとメモリリークが発生します。
弱い参照の利用
弱い参照とは、たとえ存在していても、ガーベジコレクションの際に参照としてみなされない参照です。
これは大変便利で、イベントリスナにより生成される参照が全て弱い参照であれば、参照の方向とか気にせず登録したリスナを放置しておいて良いことになるからです。
都合の良いことに、イベントリスナの登録時に弱い参照を使うかどうかを指定することができます。addEventListener() の5 つ目の引数を true にすれば弱い参照が使われます。
addEventListener(”type”, listener, false, 0, true)
3 つ目と 4 つ目の引数は false と 0 を指定しておけば大抵問題ありませんので、上の形を覚えておけば大丈夫です (たぶん)。今後イベントリスナを登録する際はこれを使うように決めてしまうというのも結構お勧めかもしれません。
Posted by ackie at 06:09 PM | Comments (1)
December 13, 2005
Sprite とマウスイベント
Sprite クラスのごく簡単な紹介は以前にしましたが、今回はマウスイベントの使い方についての補足です。最初に関連するプロパティをまとめて挙げておきます。(最初の2つは親クラスからの継承です)
public mouseEnabled : Boolean // マウスイベントを受け取るか public mouseChildren : Boolean // 子オブジェクトがマウスイベントを受け取るか public buttonMode : Boolean // ボタンとして振舞うか public hitArea : Sprite // マウスイベントの対象となる画面上の領域 public useHandCursor : Boolean // カーソルの形状を手の形にするか
AS3 でボタンを作成する場合、まずは SimpleButton の使用が考えられますが、ちょっと凝ったものがほしい場合は代わりに Sprite を使うことができます。その場合、ボタンとして使用する Sprite の中に、さらに別の Sprite を子オブジェクトとして配置して演出を加えることも多いでしょう。
さて、このとき次のような問題が発生します。ボタンとして使用する Sprite の中にある演出用の Sprite もマウスイベントの対象となることが可能です。そのため、ボタン用の Sprite ではなく、その中の Sprite がイベントのターゲットになってしまうケースです。(AS3 では DisplayObject のサブクラスを追加すると、意図していなくてもイベントフローが変わってしまいます。)
ある Sprite がマウスイベントの対称でないことを明示的に示すには mouseEnabled プロパティを false に設定すれば可能です。Sprite 一つ一つについて設定を管理するのが面倒であれば、ボタン用 Sprite の mouseChildren プロパティを false にする方法もあります。これにより、ボタン用 Sprite の子オブジェクトは全てイベントの対象から外され、イベントの target は子オブジェクトの上でクリックされてもボタン用の Sprite になります。こっちの使用方法がたぶん使いやすいでしょう。
その他に、hitArea プロパティを使用して、マウスイベントのターゲットとなる Sprite を自身以外に指定することもできます。この場合は、hitArea で指定した Sprite の mouseEnabled を false にします。そうしないと、hitArea で指定された側の Sprite がイベントを受け取ってしまいます。
buttonMode プロパティを true に設定しておくと、マウスが Sprite の上に来たときマウスの形状を自動的に手の形に変えてくれます。(useHandCursor プロパティのほうが優先されます) MovieClip と違い Sprite にはタイムラインが無いため、マウスの動きにあわせて自動的に表示を変えることはできません。
Posted by ackie at 05:02 PM | Comments (0)
December 12, 2005
カスタムイベントのディスパッチ
Event クラスを拡張すると、自前のイベントクラスを定義することができます。例えば、以下のような感じです。(あまり意味のない例ですが...)
public class CustomEvent extends Event {
public function CustomEvent() {
super("customEvent");
}
public override function clone():Event {
return new CustomEvent();
}
}
(Event のサブクラスを実装する際は、clone() メソッドを実装する必要があります。override キーワードについては別の機会に説明します。)
EventDispatcher クラスの記事で紹介した dispatchEvent() メソッドを使うと、自分でイベントのディスパッチができます。例えば Sprite は EventDispatcher のサブクラスでしたから、次のようにして CustomEvent をディスパッチできます。
public class CustomEventSprite extends Sprite {
public function dispatchCustomEvent() {
dispatchEvent(new CustomEvent());
}
}
あとはイベント処理を呼び出すためのイベントリスナーを登録すれば完了です。
public class EventTest extends Sprite {
public function EventTest() {
var ceSprite:CustomEventSprite = new CustomEventSprite();
ceSprite.addEventListener("customEvent", onCustomEvent);
ceSprite.dispatchCustomEvent();
}
private function onCustomEvent(event:CustomEvent):Void {
// ここにイベント処理を記述
}
}
以上が、カスタムイベントの基本的な使い方です。
さて、AS3 では多重継承が許されないため、必ずしも EventDispatcher のサブクラスに出来るとは限りません。そのような場合には、IEventDispatcher インターフェースを使用します。クラス内部で EventDispatcher のインスタンスを持ち、そこに処理をデリゲートするという使い方です。
public class DecoratedDispatcher extends SomeClass implements IEventDispatcher {
var eventDispatcher:EventDispatcher;
public function DecoratedDispatcher(){
eventDispatcher = new EventDispatcher();
}
public function dispatchEvent(evt:Event):Boolean{
return eventDispatcher.dispatchEvent(evt);
}
...
...
}
本来は、全てのメソッドをデリゲートするのですが、長くなるため上の例は途中ではしょってあります。(念のため)
Posted by ackie at 04:46 PM | Comments (0)
December 09, 2005
Event クラスのメソッド
今回は Event クラスの主なメソッドについて解説します。最初の2つは、イベントに関連付けられたデフォルトの動作をキャンセルするためのものです。イベントの中で cancelable プロパティが true のものには以下のメソッドが有効です。
public preventDefault() : Void public isDefaultPrevented() : Boolean
イベントには、予め Flash Player が実行する動作が設定されているものがあります。例えば、テキストフィールド上で文字をタイプすると、テキストフィールドに入力した文字が表示されます。これは TextEventType.TEXT_INPUT 型のイベントのデフォルトの動作です。
ところで、このイベントは cancelable プロパティが true のためキャンセルすることができます。イベントに対して preventDefault() を呼び出すと、文字の表示を中止できます。
一方 EventType.ENTER_FRAME のようにキャンセルできないイベントの場合は、このメソッドを使用しても何も効果はありません。
isDefaultPrevented() メソッドは、既にデフォルトの動作がキャンセルされているかを調べるのに使います。戻り値が true であれば既に preventDefault() が呼ばれたことを示します。
さて、次の2つは、イベントフローによるリスナーの呼び出しを止めるためのメソッドです。
public stopPropagation() : Void public stopImmediatePropagation() : Void
どちらのメソッドを呼んでも、そのメソッドを呼び出したイベントリスナーが登録されているオブジェクトでイベントリスナーの呼び出しは終了します。イベントフロー上の残りのオブジェクトに登録されたイベントリスナーが呼び出されることはありません。
2つのメソッドの違いは、stopPropagation() メソッドは現在イベントを処理中のオブジェクトにまだ実行待ちのイベントリスナーが登録されていたらそれも実行するのに対し、stopImmediatePropagation() メソッドは自身が登録されているオブジェクトを含め以降のイベントリスナー呼び出しを行わないという点にあります。
注:なお、どちらのメソッドも、イベントに関連付けられているデフォルトの動作はキャンセルしません。これらのメソッドが有効な対象は自分で追加したリスナーに限定されます。デフォルトの動作をキャンセルするときには、この記事の最初に紹介した preventDefault() メソッドを使います。
Posted by ackie at 06:29 PM | Comments (0)
December 08, 2005
イベントリスナーと Event クラス
Event クラスの話を始める前に、少しイベントリスナーの登録について補足します。
まず、AS3 ではイベントフローに参加しているノードであればイベントリスナーを登録することができます。それから、中間ノードには、キャプチャーフェーズとバブルフェースでイベントが伝わりますが、イベントリスナー登録時にはこれらを区別する必要があります。addEventListener メソッドを呼ぶ際に、3つ目の引数 (参照記事はこちら) を true にするとキャプチャーフェーズ、false にするとバブリングフェーズ(またはターゲットフェーズ)にリスナー関数が登録されます。デフォルト値は false のため、特に指定しなければ、バブリングもしくはターゲットフェーズに登録されます。
イベントリスナー削除の際も、同様に、対象のフェーズを意識する必要があります。
Event クラス
さて、イベントリスナーとして登録される関数はイベントを引数として持つ関数でした。
function myListener(event:Event) {
// ここにイベント処理を記述します
}
AS3 では引数が Event クラス (livedocs@lab) のオブジェクトになっています。AS2 では Object でしたね。Event のオブジェクトには、イベントの種類やターゲットなどのプロパティがあります。
public bubbles : Boolean // バブリングイベントかどうか public cancelable : Boolean // イベント処理がキャンセルできるか public currentTarget : Object // 現在イベント処理中のオブジェクト public eventPhase : uint // 現在のイベントのフェーズ public target : Object // イベントのターゲットオブジェクト public type : String // イベントの種類
イベントフローのサポートに伴い、target と currentTarget オブジェクトの2種類のターゲットがプロパティとして定義されました。また、イベントフローを途中で止められるかどうかを示す cancelable というプロパティもあります。このトピックは次回に説明します。
type プロパティに設定されるイベントの種類は EventType というクラス (livedocs@lab) に定義されています。が、イベントの種類によっては、イベントリスナーが受け取るのは Event のサブクラスのオブジェクトだったりします。例えば、マウスイベントの場合には MouseEvent クラス (livedocs@lab) です。ちなみに、マウスイベントの種類は MouseEventType クラス (livedocs@lab) に定義されています。これらを使って、以下のような記述ができます。
function myListener(event:MouseEvent) {
if (event.type == MouseEventType.CLICK)
{
clickPoint = new Point(event.localX, event.localY);
}
}
このように MouseEvent クラスにはマウス情報に関連したプロパティがあり、例えばクリックされた座標などを知ることが可能です。詳しくはドキュメントを参照してください。
Posted by ackie at 08:23 PM | Comments (0)
December 02, 2005
イベントフロー
DisplayObjectContainer の記事のときに、ディスプレイリストと呼ばれる DisplayObject のツリー構造について触れました。AS3 のイベントモデルはディスプレイリストと密接にかかわっています。

上の図は、ディスプレイリストの一例を図として表したものです。新しいイベントモデルでは、イベントがターゲットのオブジェクトに直接ディスパッチされるのではなく、上記のような階層構造のルートからターゲットまで途中のノードを順番に経由してイベントが伝わり、そしてまたルートへと途中のノードを経由しながらイベントが戻ります。この一連の流れをイベントフローと呼びます。
イベントフローは3つのフェーズに分けて考えます。例えば Child1 がクリックされたときのイベントフローは下図になります。
最初のフェーズはルートから Child1 にイベントが伝わるフェーズでキャプチャフェーズと呼ばれます。キャプチャフェーズは図の赤で示されている範囲で Child1 の直前のノードまでです。Child1 にイベントが伝わったら、その状態がターゲットフェーズと呼ばれる2つ目のフェーズです。図中では緑で表示されています。最後の3つ目がバブリングフェーズで、Child1 直後のノード (Parent) からルートまでを指します。青で示された範囲です。
前の記事にも書いたように DisplayObject は EventDispatcher のサブクラスなので、各ノードにはイベントリスナーを登録できます。そのため、他のオブジェクトに対するイベントであってもイベントフローの中に含まれるオブジェクトであればイベント処理を呼び出すことが可能です。例えば、上図で Child1 がクリックされたときの処理を Parent ノードにも登録できるわけです。イベントを使った親子の連携処理がいろいろな場面で使えそうですね。
従来のイベントモデルはターゲットフェーズのみだったと考えることができます。AS3 でも全てのイベントが3つのフェーズを持つわけであはありません。例えば、enterFrame イベントはターゲットのオブジェクトに直接ディスパッチされバブリングは起きません。個々ののイベントが持つフェーズついては該当するドキュメントを参照してください。
Posted by ackie at 06:32 PM | Comments (0)
November 30, 2005
EventDispatcher クラス
ActionScript3 では新しいイベントモデルが導入されました。AS2 では、場合によって何種類ものイベントを使い分けなければいけなかったり、さらに、使い方によってイベントのスコープが異なったりと、使いこなすにはそれなりの熟練が要求されましたが、AS3 からは DOM Level 3 イベントモデルをベースに統一されたイベントモデルを使用します。
EventDispatcher クラス
EventDispatcher は新しいイベントモデルの基本となる機能を実装したクラスです。 イベントをディスパッチしたりイベントのターゲットになったりします。また EventDispatcher は前に紹介した DisplayObject の親クラスです。flash.display パッケージの中で取り上げたクラスがイベントを処理する機能を持っていたのは EventDispatcher の機能を継承しているためです。
イベントリスナーの登録/削除にはそれぞれ addEventListener() と removeEventLister() を使います。どちらのメソッドも最初の2つの引数が必須で、指定するのは監視対象のイベントの種類とイベントを処理するリスナー関数です。
public addEventListener(type:String, listener:Function,
useCapture:Boolean = false, priority:int = 0) : Void
public removeEventListener(type:String, listener:Function,
useCapture:Boolean = false) : Void
少し面白いのはリスナー登録時にプライオリティを指定できることです。(4番目の引数) これで例えば、リスナーを呼び出す際の事前/事後の条件処理を行うことが可能になります。同じプライオリティでいくつも登録できるので、一般のリスナーはデフォルト(0)で登録しておいて、事前条件チェック用のメソッドをプライオリティを -1 に、事後条件チェック用はプライオリティ 1 にすればよいわけです。ちなみに同じプライオリティのリスナー同士では登録順に呼び出されます。
下の2つはリスナーの登録状況を調べるのに便利な関数です。
public hasEventListener(type:String) : Boolean public willTrigger(type:String) : Boolean
hasEventListner() も willTrigger() も、特定のイベントに対するリスナーが登録されているかを調べるメソッドです。両者の違いは、hasEventListener() はメソッドが所属する EventDispatcher インスタンスについてのみリスナーが登録されているかを調べるのに対して、willTrigger() はイベントフロー全体の中で対象のイベントに対するリスナーが登録されているかを調べることです。
さて、イベントフローという言葉が出てきました。AS3 では一つのイベントが複数のオブジェクトを渡り歩きます。次回はここをも少し詳しく解説します。
最後にイベントをディスパッチするメソッドも紹介して起きます。
public dispatchEvent(event:Event) : Boolean
Posted by ackie at 08:01 PM | Comments (0)