こんにちはtaroです.

最近AS3をあまり書いていないのですが、せっかくなのでAS3ネタを不定期に書いていこうと思いました。


ご存知の通り、for..in, for each..inは使えるケース・使えないケースがあってそんなに扱いのし易い文法ではありません。


例えば、次のようなケース


class A {
public var var0:String;
public var var1:String;
}

というようなpublicなプロパティをもつクラスのインスタンスについて


var a:A = new A;
for (var key:String in a) {
trace(key);
}

のように書いてもvar0, var1という風にはtraceされません。もちろんflash.utils.Proxyを使えば他の言語のようにイテレーターのようなものを定義してやることも可能です。


class AProxy extends Proxy {
private var _names:Array = ["var0", "var1"];
private var _a:A = new A;

override flash_proxy function nextNameIndex (index:int):int {
return (index < _names.length) ? index + 1 : 0;
}
override flash_proxy function nextName(index:int):String {
return _names[index - 1];
}
override flash_proxy function nextValue(index:int):* {
return _a[flash_proxy::nextName(index)];
}
}

しかし、これでは、ただプロパティーを列挙したいだけなのに冗長すぎます。ではどうするか?describeTypeを使うというのは一つの手でしょう。


describeType


この関数はクラスの型情報をXMLで教えてくれます。例えば上で挙げたクラスAでやってみると、


describeType(new A);

<type name="::A" base="Object" isDynamic="false" isFinal="false" isStatic="false">
<extendsClass type="Object"/>
<variable name="var0" type="String"/>
<variable name="var1" type="String"/>
</type>

といった感じのXMLになります。あるいは、無意味に型情報を取るだけにインスタンス化をしたくないときもあるので、クラスへの参照を渡したいときもあるでしょう。その場合は、このようになります。


describeType(A);

<type name="::A" base="Class" isDynamic="true" isFinal="true" isStatic="true">
<extendsClass type="Class"/>
<extendsClass type="Object"/>
<accessor name="prototype" access="readonly" type="*" declaredBy="Class"/>
<factory type="::A">
<extendsClass type="Object"/>
<variable name="var0" type="String"/>
<variable name="var1" type="String"/>
</factory>
</type>

いずれにしてもXMLの子孫の<variable>というタグを訪問すれば良さそうです.


for each(var key:String in describeType(a)..variable.@name) {
trace(key);
}

e4xの".."を使ったのは、describeTypeに対してクラスの参照を渡すか、インスタンスを渡すかという区別をつけないためです.


インスタンスしか使わないよ!ってときには"."でも問題ないでしょう


for..inとの違い


for..inはArrayやObject, Dictionary, XML等のキーを列挙するためのものでした. 自分で作ったカスタムのクラスにはこのAPIは提供されておらず、 自前でProxyを書かないと駄目でした.


最後に具体的な使う場面を考えてみます.


ユースケース


僕はFlashは素材swc書き出し用 / コンパイルはflexコンパイラっていう風にやっていますが、Flashでムービークリップに書き出しクラスを設定してSWC書き出す際に、子供のムービークリップに名前をつけると、 その名前のpublicなプロパティーが書き出しクラスに設定されます. 例えば下のようにボタンが沢山あるようなコンポーネント.


 

このコンポーネントを例えば、assets.MenuのようなクラスとしてSWC書き出しをして、SWCビューワーのようなものでみると、


package assets
{
import flash.display.MovieClip;
import fl.controls.Button;

public class Menu extends MovieClip
{
public var buttonA : Button;
public var buttonB : Button;
public var buttonC : Button;
public var buttonD : Button;

public function Menu ();
}
}


というように書き出されます。なので・・・


var menu:Menu = new Menu;
addChild(menu);
var button:Button;
for each(var buttonName:String in describeType(menu)..variable.@name) {
button = menu[buttonName];
button.addEventListener(MouseEvent.CLICK, onButtonClick);
}

function onButtonClick(event:MouseEvent):void {
//ボタン名はevent.currentTarget.nameで取得できる
}


のように書くこともできます。あるいは、全部別々のイベントハンドラーにしたいよ!って時なんかは、


var menu:Menu = new Menu;
addChild(menu);
var button:Button;
var callbackName:String;
for each(var buttonName:String in describeType(menu)..variable.@name) {
button = menu[buttonName];
callbackName = 'on' + buttonName.replace(/\w/, toUpperCase) + 'Click';
button.addEventListener(MouseEvent.CLICK, this[callbackName]);
}

function toUpperCase(value:String, ...rest):String {
return value.toUpperCase();
}

...

private function onButtonAClick(event:MouseEvent):void { }
private function onButtonBClick(event:MouseEvent):void { }
private function onButtonCClick(event:MouseEvent):void { }
private function onButtonDClick(event:MouseEvent):void { }


なんてやってもいいでしょう。


最後に


describeTypeの欠点と言えば、ObjectやArray等に対しては使えないことですね. じゃぁ、それも補ってみようということで汎用的なforEach, forIn関数を定義してみます.


function forEach(obj:*, callback:Function):void {
var typeInfo:XML = describeType(obj);
var key:String;
if (typeInfo.children().length() == 0 ||
typeInfo.@name == 'Array') {
for (key in obj) callback(obj[key]);
} else {
for each (key in typeInfo..variable.@name) callback(obj[key]);
}
}

function forIn(obj:*, callback:Function):void {
var typeInfo:XML = describeType(obj);
var key:String;
if (typeInfo.children().length() == 0 ||
typeInfo.@name == 'Array') {
for (key in obj) callback(key);
} else {
for each (key in typeInfo..variable.@name) callback(key);
}
}


使い方は、


forEach(obj, trace); // objのプロパティが列挙される
forIn(obj, trace); // objのキーが列挙される

という感じです。勿論パフォーマンス面では律儀にfor文などを使うのと比べてやや劣るかも知れませんが、イベントハンドラを設定するスクリプトなんかは高々100未満のループなので、違いはほぼ無いでしょう。

forEach, forIn - wonderfl build flash online

HTML5飯