2008年8月アーカイブ
何かと AS3 の Proxy クラスは使えないという声をよく目にします。
特に目立つのが他のクラスを継承できないこと。
例えば DisplayObject の Proxy を作ったとしても、元がProxyクラスなので addChild() が使えないとか。たぶんそんなの。
今回はその辺について、ちょっと思ったことを休み前にまとめとこうかと。
Progression使いたいけど、自前で拡張したクラスも組み合わせて、 なんか有効にいろんなライブラリに手を出せないかな。と思ったのが きっかけでして、そこで一番に思い当たるのはやっぱりProxyクラスだったわけです。
で最初はProxy使った方法をいろいろごにょごにょと考えてたんですけど、 結局 addChild() や removeChild() で指定するためには、どうしても DisplayObject を 参照しなきゃいけなくなって、それが参照できる時点でProxyの役目は破綻してしまうんです。
例えば上記の場合、親のDisplayObjectにProxyクラスが内包するDisplayObjectを displayというプロパティ経由で入れ子にしようとしています。
でも、これをしてしまうと、以降、親DisplayObjectからは本来隠蔽しておきたいはずの 子DisplayObjectを参照できるようになってしまい、 Proxyを通さずに直接参照・変更ができてしまいます。
そもそも Proxy は dynamic ステートメントを付けておかないと 効果を発揮しません。
つまり、頑張って DisplayObjectProxy とか EventProxy とかを作っても、 それを継承して様々なクラスに派生させていくような作り方だと、 全部ダイナミッククラスになってしまって はちゃめちゃになってしまいます。
もしこういう使い方をしたいならば、 素直にDisplayObject のサブクラスを作って、中身をすべて 内包する他のオブジェクトに委譲したほうがいいかもしれません。
ただ、DisplayObject で Proxy クラスを作成した場合でも、 結局のところ addChild には若干問題が残ります。
Sprite や MovieClip なら、委譲先のオブジェクトを DisplayObjectContainer から作成した Proxy 用クラスのコンテナ内に追加しておけば addChild() 時に画面上に現れますが、TextField や Bitmap は親オブジェクトになれないので、これらを継承したProxyクラスは addChild しても画面に表示されません。
あくまで追加されるのは Proxy クラスだからです。
addChild はまだマシな方で、ADDEDイベント発生時に内部処理で置き換えが可能ですが、 removeChild 時にはどう頑張ってもクライアント側で一工夫しないといけなくなります。
結局いろいろ試した結果、僕は addChild の問題は放棄しました。
Proxyを末端の子オブジェクトとして捉える限り、この問題は解決しないように思えたからです。
ProxyならProxyらしくコンテナ側に回してやればいいんじゃないかと。
で、以下はDisplayNodeというProxyクラスを継承したサブクラスを作ってみた場合の一例です。
出力結果
何をやってるかというと、
child2 は delete の実行によって、内部で removeChild してます。
このクラスの中身も一応参考までにアップしておきます。
こういう使い方もあるってことで。
DisplayNode
DisplayObjectを継承したProxyを作りたいときも、なんとなくそれっぽいものまでは作る事ができます。
要はダイナミッククラスな訳ですから、以下のような処理で各メソッドの呼び出し時の処理に手を加えることも。
テキストフォーマットを吐き出すSpriteとか。
ただし、getter と setter は動的に追加できないから、やっぱり完全なProxyクラスの代用は難しいと思う。
上の例の場合はプロパティへのアクセス用メソッドを用意していて、text("hoge") みたいにして使う感じ。
TestProxy
結局のところ TestProxy みたいに無尽蔵にメソッドを追加しちゃうようなものは
実際にはあんまり使いどころがない気がする。コードヒントとか出ないし。
現在 DisplayObject を継承した Proxy 用クラスで、比較的使用頻度の高いのは以下のようなクラス。
こういうクラスを予め用意しておくと、結構役に立ってくれたりします。
SpriteProxy
このクラスはこのままインスタンスを作ってもまったく意味のないクラスなので継承しないと使えません。
これって Proxy じゃなくて Decorator な気もするけど、
その辺そんなに詳しいわけでもないので気にしない。
今回は DisplayObject と一緒に使う事に絞ってみたけど、 Proxy クラス自体はアクセス方法がかなり自由なのでいろんな使い方ができると思います。
中でも MXML みたいな使い方は得意なんじゃないかと。
あんまり多用すると後が怖いですけども。
それにしても、とりとめのない文章だなあ。
特に目立つのが他のクラスを継承できないこと。
例えば DisplayObject の Proxy を作ったとしても、元がProxyクラスなので addChild() が使えないとか。たぶんそんなの。
今回はその辺について、ちょっと思ったことを休み前にまとめとこうかと。
Progression使いたいけど、自前で拡張したクラスも組み合わせて、 なんか有効にいろんなライブラリに手を出せないかな。と思ったのが きっかけでして、そこで一番に思い当たるのはやっぱりProxyクラスだったわけです。
で最初はProxy使った方法をいろいろごにょごにょと考えてたんですけど、 結局 addChild() や removeChild() で指定するためには、どうしても DisplayObject を 参照しなきゃいけなくなって、それが参照できる時点でProxyの役目は破綻してしまうんです。
var parentObject:DisplayObjectContainer var childObject:Proxy parentObject.addChild(childObject.display);
例えば上記の場合、親のDisplayObjectにProxyクラスが内包するDisplayObjectを displayというプロパティ経由で入れ子にしようとしています。
でも、これをしてしまうと、以降、親DisplayObjectからは本来隠蔽しておきたいはずの 子DisplayObjectを参照できるようになってしまい、 Proxyを通さずに直接参照・変更ができてしまいます。
そもそも Proxy は dynamic ステートメントを付けておかないと 効果を発揮しません。
つまり、頑張って DisplayObjectProxy とか EventProxy とかを作っても、 それを継承して様々なクラスに派生させていくような作り方だと、 全部ダイナミッククラスになってしまって はちゃめちゃになってしまいます。
もしこういう使い方をしたいならば、 素直にDisplayObject のサブクラスを作って、中身をすべて 内包する他のオブジェクトに委譲したほうがいいかもしれません。
ただ、DisplayObject で Proxy クラスを作成した場合でも、 結局のところ addChild には若干問題が残ります。
Sprite や MovieClip なら、委譲先のオブジェクトを DisplayObjectContainer から作成した Proxy 用クラスのコンテナ内に追加しておけば addChild() 時に画面上に現れますが、TextField や Bitmap は親オブジェクトになれないので、これらを継承したProxyクラスは addChild しても画面に表示されません。
あくまで追加されるのは Proxy クラスだからです。
addChild はまだマシな方で、ADDEDイベント発生時に内部処理で置き換えが可能ですが、 removeChild 時にはどう頑張ってもクライアント側で一工夫しないといけなくなります。
結局いろいろ試した結果、僕は addChild の問題は放棄しました。
Proxyを末端の子オブジェクトとして捉える限り、この問題は解決しないように思えたからです。
ProxyならProxyらしくコンテナ側に回してやればいいんじゃないかと。
で、以下はDisplayNodeというProxyクラスを継承したサブクラスを作ってみた場合の一例です。
var rootNode:DisplayNode = new DisplayNode(new Sprite()); rootNode.child1 = new Sprite(); rootNode.child1.grandchild1 = new Shape(); rootNode.child1.grandchild2 = new Shape(); rootNode.child1.grandchild3 = new Sprite(); rootNode.child1.grandchild3.addChild(new Bitmap()); rootNode.child2 = new Sprite(); rootNode.child2.dummy = new Sprite(); rootNode.child3 = new Sprite(); delete rootNode.child2; trace(rootNode);
出力結果
instance148 [object Sprite]
child1 [object Sprite]
grandchild1 [object Shape]
grandchild2 [object Shape]
grandchild3 [object Sprite]
instance153 [object Bitmap]
child3 [object Sprite]
何をやってるかというと、
private var _target:DisplayObjectContainer;
override flash_proxy function getProperty(name:*):*
{
var child:DisplayObject = _target.getChildByName(name);
if (child is DisplayObjectContainer)
return new DisplayNode(child as DisplayObjectContainer);
return child;
}
override flash_proxy function setProperty(name:*, value:*):void
{
if (value is DisplayObject)
{
value.name = name;
_target.addChild(value);
}
}
setProperty と getProperty を使って表示オブジェクトを
参照名で addChild していく。ってもの。child2 は delete の実行によって、内部で removeChild してます。
このクラスの中身も一応参考までにアップしておきます。
こういう使い方もあるってことで。
DisplayNode
DisplayObjectを継承したProxyを作りたいときも、なんとなくそれっぽいものまでは作る事ができます。
要はダイナミッククラスな訳ですから、以下のような処理で各メソッドの呼び出し時の処理に手を加えることも。
package
{
import flash.display.DisplayObject;
import flash.display.Sprite;
import flash.utils.describeType;
dynamic public class TestProxy extends Sprite
{
public function TestProxy(target:DisplayObject)
{
setProxyMethods(target);
}
public function setProxyMethods(target:DisplayObject):void
{
var data:XML = describeType(target);
var n:uint, i:uint, name:String;
var methods:XMLList = data.elements("method");
n = methods.length();
for (i = 0; i < n; i++ )
{
name = methods[i].@name;
if (!this.hasOwnProperty(name))
{
this[name] = function():*
{
var func:Function = target[arguments.callee.methodName];
return func.apply(target, arguments);
}
this[name].methodName = name;
}
}
var accessor:XMLList = data.elements("accessor");
n = accessor.length();
for (i = 0; i < n; i++ )
{
name = accessor[i].@name;
if (!this.hasOwnProperty(name))
{
this[name] = function():*
{
if (arguments.length == 1)
return target[arguments.callee.propName] = arguments[0];
return target[arguments.callee.propName];
}
this[name].propName = name;
}
}
}
}// end of class
}// end of package
var proxy:TestProxy = new TestProxy(new TextField());
proxy.appendText("hoge");
trace(proxy.text()); // 出力 : hoge
trace(proxy.getTextFormat()); // 出力 : [object TextFormat]
テキストフォーマットを吐き出すSpriteとか。
ただし、getter と setter は動的に追加できないから、やっぱり完全なProxyクラスの代用は難しいと思う。
上の例の場合はプロパティへのアクセス用メソッドを用意していて、text("hoge") みたいにして使う感じ。
TestProxy
結局のところ TestProxy みたいに無尽蔵にメソッドを追加しちゃうようなものは
実際にはあんまり使いどころがない気がする。コードヒントとか出ないし。
現在 DisplayObject を継承した Proxy 用クラスで、比較的使用頻度の高いのは以下のようなクラス。
こういうクラスを予め用意しておくと、結構役に立ってくれたりします。
SpriteProxy
このクラスはこのままインスタンスを作ってもまったく意味のないクラスなので継承しないと使えません。
これって Proxy じゃなくて Decorator な気もするけど、
その辺そんなに詳しいわけでもないので気にしない。
今回は DisplayObject と一緒に使う事に絞ってみたけど、 Proxy クラス自体はアクセス方法がかなり自由なのでいろんな使い方ができると思います。
中でも MXML みたいな使い方は得意なんじゃないかと。
あんまり多用すると後が怖いですけども。
それにしても、とりとめのない文章だなあ。
リンケージ設定JSFLコマンド
LinkageSettings.mxp
自分で使えよって話なんですが。
上記以外は変わってないですが、今回の修正で結構便利度アップしたと思いますよ。
例えば
ステージ上に配置されたシンボル「HomeButton」「WorksButton」「ContactButton」の3つを選択して
このコマンドを実行した場合、
クラスファイルの記述もちょいちょい設定できます。
使い方は前回の記事のままです。
不明点があればどうぞ遠慮なく聞いてください。
以前の記事で、複数のシンボルがどうのこうのと注意事項で書いてますが、
特に気にしなくても使えると思います。
インスタンス名にシンボル名を設定するJSFLコマンド
setInstanceName.mxp
ステージ上のインスタンス名は「testButton」になります。
変更はこれだけですが、AS3では思いのほか便利になったと思います。
上の「リンケージ設定コマンド」と併用するとさらに便利。
クラス名はシンボル名が適用されて、インスタンス名は先頭文字だけ小文字
って状況はAS3では良くあるんじゃないでしょうか。
思い当たる人は是非使ってみてくださいませ。
--追記
なんかディレクトリ構成の例が変だけど気にしないで(汗
--更に追記
Embedタグの記述箇所が変だったのと、symbolパラメータの値が正しく入力されてなかったので
再修正してアップしました。
LinkageSettings.mxp
--しつこくも更に追記
クラス作成時、インスタンス変数を追加する際に、変数の型をすべて「DisplayObject」にしていましたが
クラスの割り当てがされているインスタンスについては、設定されたクラスを追加するようにしました。
指定クラスファイルが存在しない場合は基本クラスに設定されているクラスが指定されます。
さらに基本クラスがデフォルトである場合は DisplayObject が指定されます。
(基本クラスが Sprite や MovieClip 等である場合、JSFLでは判定できないため)
また、上記変更に伴い、import 文が長く(複数行)になる場合があるので
import 文はソートした状態で書き加えられるように変更しました。(可読性向上のため)
LinkageSettings.mxp
LinkageSettings.mxp
- クラスパスに「/」が入力できなかったのを修正
- 参照ボタンが正常に動作していなかったので修正
- 「ActionScriptに書き出し」にデフォルトでチェックが入るように変更
- 「ActionScriptに書き出し」にチェックが入っていないときは「クラスファイルを作成する」を選択できないように変更
自分で使えよって話なんですが。
上記以外は変わってないですが、今回の修正で結構便利度アップしたと思いますよ。
例えば
/project
/bin
index.fla
/src
Main.as
上記のようなディレクトリ構成で、index.flaを開いて作業いていた状態でステージ上に配置されたシンボル「HomeButton」「WorksButton」「ContactButton」の3つを選択して
このコマンドを実行した場合、
クラスパス: ../src クラス名: demo.buttons.* 基本クラス: *上記の設定で「クラスファイルを作成する」にチェックを入れて実行すると
/project
/bin
index.fla
/src
Main.as
/demo
/buttons
HomeButton.as
WorksButton.as
ContactButton.as
上記のようにファイルが作成されます。クラスファイルの記述もちょいちょい設定できます。
使い方は前回の記事のままです。
不明点があればどうぞ遠慮なく聞いてください。
以前の記事で、複数のシンボルがどうのこうのと注意事項で書いてますが、
特に気にしなくても使えると思います。
インスタンス名にシンボル名を設定するJSFLコマンド
setInstanceName.mxp
- 先頭の文字を小文字に変換する
ステージ上のインスタンス名は「testButton」になります。
変更はこれだけですが、AS3では思いのほか便利になったと思います。
上の「リンケージ設定コマンド」と併用するとさらに便利。
クラス名はシンボル名が適用されて、インスタンス名は先頭文字だけ小文字
って状況はAS3では良くあるんじゃないでしょうか。
思い当たる人は是非使ってみてくださいませ。
--追記
なんかディレクトリ構成の例が変だけど気にしないで(汗
--更に追記
Embedタグの記述箇所が変だったのと、symbolパラメータの値が正しく入力されてなかったので
再修正してアップしました。
LinkageSettings.mxp
--しつこくも更に追記
クラス作成時、インスタンス変数を追加する際に、変数の型をすべて「DisplayObject」にしていましたが
クラスの割り当てがされているインスタンスについては、設定されたクラスを追加するようにしました。
指定クラスファイルが存在しない場合は基本クラスに設定されているクラスが指定されます。
さらに基本クラスがデフォルトである場合は DisplayObject が指定されます。
(基本クラスが Sprite や MovieClip 等である場合、JSFLでは判定できないため)
また、上記変更に伴い、import 文が長く(複数行)になる場合があるので
import 文はソートした状態で書き加えられるように変更しました。(可読性向上のため)
LinkageSettings.mxp
