akihiro kamijo: ActionScript 3.0 Archives

August 28, 2008

Flash Player 10 の動的サウンド生成機能 (Sound クラス)

Flash Player 10 から動的に生成したサウンドを再生する機能がサポートされています。ベータ 2 で変更された点などもありますので遅ればせながらご紹介します。

まず、この機能を使うには Sound オブジェクトに Event.SAMPLE_DATA のイベントハンドラを設定します。そうすると Sound オブジェクトの動作が通常と変わり、定期的にイベントハンドラを呼び出しては再生するデータを読み込むようになります。このとき SoundChannel は読み込まれたデータを一連のオーディオデータストリームとして再生します。

var mySound:Sound = new Sound();
mySound.addEventListener(Event.SAMPLE_DATA, onSampleData);
var myChannel:SoundChannel = mySound.play();
 
function onSampleData(event:SampleDataEvent):void 
{
  // この中で SoundChannel にデータを書き込む
}
 

SAMPLE_DATA 用イベントハンドラ内では、イベントオブジェクトから渡される ByteArray オブジェクトにオーディオデータを書き込みます。イベントオブジェクトの型は Flash Player 10 から新しく追加された sampleDataEvent で、以下の属性が利用できます。

public data : ByteArray   // オーディオストリーム内のデータ
public position : Number  // オーディオストリーム内の位置
 

ByteArray オブジェクト (event.data) に書き込む値は 32 ビット big endian 浮動小数点数形式の 44.1KHz ステレオデータです。従って一つのサンプルデータの大きさは 8 バイト (4 バイトのデータが左右それぞれ) になります。

下は sampleData イベントハンドラの例です。単純なサイン波形をデータとして書き込んでいます。再生すると聴覚検査で聴くような音がすると思います 。

function onSampleData(event:SampleDataEvent):void {
  for (var c:int=0; c<8192; c++) {
    var rad:Number = Number(c+event.position)/Math.PI/2;
    var amp:Number = Math.sin(rad) / 4;  // -1 から 1 の間の値なら OK
    event.data.writeFloat(amp);  // 左チャネルの音
    event.data.writeFloat(amp);  // 右チャネルの音
  }
}
 

データの計算式を変えると音色が変わると思いますので試してみてください。その際、サンプルデータの値が -1 から 1 の範囲を超えないようにご注意を。

さて、イベントハンドラ内では 2048 以上 8192 以下のサンプルデータを書き込みます。パフォーマンス上はできるだけ多くのサンプルデータを (つまり 8192 個) 書くのが有利と考えられます。

書き込まれたデータが 2048 以下の場合には、データを全て再生した時点で SoundChannel は再生を停止し soundComplete イベントを生成します。明示的に再生を停止したいときには SoundChannel.stop() を呼ぶこともできます。

sampleData イベントにより書き込みを求められている場所と再生中の箇所との時間差は以下の式で求めることができます。ここでの単位は ms になります。

event.position/44.1 - myChannel.position
 

あとは、Sound オブジェクトからオーディオデータを取得するために extract() というメソッドが用意されています。ある音源を読み込んだ Sound オブジェクトからオーディオデータを取り出して他の Sound オブジェクトで動的に再生したりするのに使えます。メソッドの定義は以下のようになっています。

public extract(target:ByteArray, length:Number, startPosition:Number = -1):Number // 
 

extract() は target で指定した ByteArray に length 分のサンプルデータを抽出します。startPosition は読み込みを開始するポジションの指定ですが、明示的に指定しないと、一回目はデータの先頭から、それ以降は続きから順番に読み込みます。

なお、Sound オブジェクトに sampleDataEvent 用のイベントハンドラを設定すると load() 等のメソッドは使えなくなります。

sampleDataEvent.data に書き込むのは 32 ビット big endian なので ShaderJob の結果がそのまま使えそうですね。

Posted by ackie at 06:53 PM | Comments (0)

August 18, 2008

ECMAScript Harmony/ES3.1 と ActionScript

既に一部で伝えられているように、先週 ECMA TC39 から次期 ECMAScript 標準に関するアナウンスがありました。

Flash Player 9 の発表以来、Adobe は ActionScript を ECMAScript 標準第 4 版として提案された ECMA-262 Edition 4 (ES4) に完全準拠させるという目標を公にしてきました。この ES4 は、Adobe, Mozilla, Opera, それから Google を主要なサポーターとして標準化が進められていましたが、一年ほど前に Microsoft と Yahoo! 主導で ECMA-262 Edition 3.1 (ES3.1) のワーキンググループが開始されて以来、2 つの異なる ES3 後継仕様案が並存する状況が続いていました。

先週の発表は、ES4 に関する標準化作業を中止し ES3.1 に集中することが決定されたというもので ECMAScript Harmony プロジェクトと名付けられています。ES3.1 は ES4 のサブセットではありません。つまり AS3.0 は ES3.1 非互換になるということです。例えば ES3.1 の仕様には namespace や package は含まれません。これらの機能は ES3.1 以降の拡張に際しても含まれないことがはっきりと述べられています。また、型指定や継承といった機能の採用については (そもそも class の定義自体が異なるのですが) 今後の議論を待つことになります。JS1.7 や JS1.8 として Firefox 2, 3 に追加された機能も Harmony 路線では見直されるようです。

この件に関して Adobe からの正式なコメントはまだありませんが、Adobe のオープンソースチームのディレクターである Dave がコメントを blog に公開しています。(Standards, ECMAScript and representing the past

要約すると、

  • ECMAScript 標準化委員会参加企業の利害の不一致により仕様の一本化が難しい状況になっていた
  • Web に共通の言語を持つことが必要という観点から Harmony に賛同する
  • その上で、Web の革新を更に進めるため ActionScript の拡張は続ける
  • オープンソースコミュニティ活動の継続・発展も標準化委員会との活動と同様に重要

ということで、ECMAScript の標準化には引き続き参加する一方 ES3.1 のレベルまで AS3 の機能を戻すということは考えられていないようです。(blog 記事に対するコメントには、これを機会に ECMAScript 準拠をやめちゃえというものもいくつか見られました - 例えば private コンストラクタや関数オーバーロード機能の実装など...) 既に多くのユーザに使用されている言語を、その可能性を制限する方向で変更するのは Adobe の理念に反すると Dave としては思っている様。

いましばらく、正式なコメントの発表はお待ちください。

Posted by ackie at 09:47 AM | Comments (1)

July 08, 2008

Flash Player 10 のローカルファイルアクセス機能 (FileReference クラス)

Flash Player 10 ではローカルファイルを直接 Flash アプリケーションに読み込んだり Flash アプリケーション内のデータを直接ローカルファイルに書き出す機能が追加されています。

Flash Player 9 でも FileReference を使ってローカルファイルにアクセスすることは可能です。が、その機能はファイルのアップロード/ダウンロードを行うためのもので、Flash アプリケーションからは、一旦サーバを経由しないとローカルファイルのデータを扱うことができませんでした。直接ファイル I/O の出来る日が来ることを心待ちにしていた方も多いことでしょう。

というわけで、Flash Player 10 では flash.net.FileReference クラスに以下の API が追加されています。

public function get data():ByteArray // 読み込まれたデータ (読み取り専用のプロパティ)
public function load():void          // 指定されたファイルの読み込み開始
public function save(data:*, name:String = null):void 
                                     // 保存先を選択するダイアログを表示、その後データ保存開始
 

ファイルの読み込みを開始するには load() メソッドを使用します。読み込むファイルの指定は、browse() メソッドを呼ぶとダイアログが表示されるので、その中でユーザが行います。

このときプログラムから読み込むファイルを指定することはできません。これは SWF がロードされたら勝手にローカルファイルを送信してしまうといった類のアタックからユーザを保護するためです。

load() は呼び出されるとすぐに戻るので、その後の進み具合はイベント経由で確認することになります。このあたりは従来の FileReference の使い方と同じです。

下は、ファイルを読み込む簡単なサンプルです。

var fr:FileReference=new FileReference();
fr.addEventListener(Event.SELECT,onSelect);
fr.browse(); // ファイル選択のダイアログを表示
 
function onSelect(e:Event):void
{
  // イベントリスナーを追加
  fr.addEventListener(ProgressEvent.PROGRESS,onProgress);
  fr.addEventListener(Event.COMPLETE,onComplete);
  // 下の 2 つはとりあえずコメントアウト
  // fr.addEventListener(Event.OPEN, onOpen);
  // fr.addEventListener(IOErrorEvent.IO_ERROR, onIoError);
  fr.load(); // 読み込み処理を開始
}
 
function onProgress(e:ProgressEvent):void
{
  trace("読み込んだバイト数:" + e.bytesLoaded + "、 全体のバイト数:" + e.bytesTotal);
}
function onComplete(e:Event):void
{
  trace(fr.data);
  // fr.type を参考にオブジェクト変換する等の処理を記述
  // fr.removeEventListener(...)
}
 

browse() メソッドに対してユーザがファイルを選択すると SELECT イベントが発行されます。上のサンプルではこのタイミングで必要なイベントハンドラを設定して load() を呼び出しています。

読み込み処理が成功すると COMPLETE イベントが発行されます。data プロパティに有効な値が設定されるのはこれ以降です。上の例でも COMPLETE イベントハンドラ内で値を参照しています。

さて、ローカルファイルにデータを保存する場合は save() メソッドを使用します。保存するデータは save() の第一引数に指定します。サポートされるデータの型は以下の 3 種類です。それ以外の型の場合は toString() で変換することで String として扱うようです。

  • String : UTF-8 のテキストファイルとして保存
  • XML : XML フォーマットで保存
  • ByteArray : データをそのまま保存

save() メソッドを呼ぶと、ユーザに保存先を選択するためのダイアログが表示されます。その際、ダイアログ内にデフォルトのファイル名を表示したい場合には save() の第二引数として指定することができます。

var fr:FileReference=new FileReference();
var dat:String ="これは UTF-8 の文字列として保存されます";
 
fr.addEventListener(Event.COMPLETE,onComplete);
fr.save(dat, "UTF8Text.dat"); // ダイアログを表示する
 
function onComplete(e:Event):void
{
  trace(fr.name); // ユーザが指定したファイル名を表示
}
 

save() では load() の 4 つのイベントに加えて SELECT と CANCEL イベントも利用できます。

例によって、操作するファイルの大きさに制限はありませんが (もちろんメモリが足りないとかは別として)、サポートされる (テストされている) のは 100MB までです。

Flash コンテンツがファイルにアクセスしないように設定したい場合は mm.cfg 内で LocalFileReadDisable を 1 にします。

Posted by ackie at 06:17 PM | Comments (6)

June 06, 2008

AS3 の論理積と論理和

最近質問を受けたので見てみたら Flash のヘルプドキュメントが古いのに気づいたので今日はこの話題です。(LiveDocs の AS3 リファレンス は最新です)

さて、まず下の例を考えてみます。

var foo:Boolean = true;
var bar:Object = {};
 
trace(foo && bar);  // 論理積 : 出力は [object Object]
trace(foo || bar);  // 論理和 : 出力は true
 

論理積と論理和の結果は、演算子の左側の式 (この場合は foo) の値で決定されます。ごく単純化すると、ルールは

論理積 (&&) : 左側の式が false なら左側の式の値、true なら右側の式の値
論理和 (||) : 左側の式が true なら左側の式の値、false なら右側の式の値
 

です。つまり、実際に論理演算が行われているわけではないのですね。演算結果の値も Boolean 型になるとは限りません。

さて、AS3 のプログラムでは Boolean もオブジェクトなので、左側の式を評価した後その値の型が何であっても同じルールを適用することができます (されます)。とにかく左辺の式を評価したら、結果を Boolean 型に変換して値を参照します。

それぞれの型を Boolean 型に変換した場合の値は以下のようになります。

Undefiend 	false
Null 		false
Boolean 	変換前のオブジェクトと同じ
Number 		0 または NaN は false それ以外の値は true
String 		空文字列は false  それ以外の値は true
Object 		true
 

ここで左辺が Boolean 型にならない例をひとつ。

var foo:String = "Hello";
var bar:Object = {};
trace(foo || bar); // 出力は Hello
 

”Hello” は空文字列でないため Boolean 型に変換した際の値は true になります。この例で使われている演算子は論理和 (||) なので、左側の式 (foo) の値が出力されるわけです。

論理積と論理和を使った条件文

上で記述したルールから想像されるとおり、論理積や論理和の演算において、演算子左側の式は必ず評価されますが、右側の式は左側の値次第で評価されたりされなかったりします。たとえば、論理積において、右側の式が評価されるのは左側の式の値が true の場合のみです。

この振る舞いを、条件文の代わりに使うことも可能です。たとえば以下の 2 つは同じ意味を持ちます。

1. if 文で記述
if (foo) {
  doSomething();
}
 
2. 論理積で記述
foo && doSomething();
 

この例とは逆に、ある条件が偽の場合のみ実行したい場合には、論理和 (||) を使うことができます。

論理積や論理和が if 文と交換可能であることは以下の例のほうが分かりやすいでしょうか。

1. if 文のみで記述
if (foo) {
  if (bar) {
    sayHello();
  }
}
2. if 文と論理積で記述
if (foo && bar) {
  sayHello();
}
3. 論理積のみで記述
foo && bar && sayHello();
 

else 文には論理和で対応できます。複雑な条件になってくるとこんがらがりやすいので積極的にお勧めはしませんが。

&&= 演算子と ||= 演算子

特に、オブジェクトを Boolean 型に変換したときの値により代入を制御したい場合には &&= と ||= 演算子が用意されています。&&= は左側のオブジェクトの値が真の場合のみ、||= は値が偽の場合のみ右側の式の評価と左側のオブジェクトへの代入を行います。

下の例では 「foo の値が空文字列以外の文字列だったらタグに変換する」 という条件を 2 種類の方法で記述しています。

1. if 文で記述
if (foo) {
  foo =  "<" + foo + "/>";
}
 
2. &&= 演算子で記述
foo &&= "<" + foo + "/>";

Posted by ackie at 06:46 PM | Comments (0)

May 22, 2008

Flash Player 10 の新テキストエンジンとマークアップ言語

まず、Adobe Labs に Flash Player 10 ベータの ASDoc が公開されています。(flashplayer10_as3langref_052008.zip: 6.46MB) それから、Flash Player 10 用 Flex SDK の Nightly Build (安定していないビルド) がダウンロードできます。(Flex 3 SDK Downloads) May 15 以降のビルドをダウンロードすれば Flash Player 10 の新しい機能を使ったアプリ開発ができます。

さて、今日の本題です。既報のとおり、Flash Player 10 には新しい高機能なテキストエンジンが搭載されます。上でご紹介した ASDoc には flash.text.engine というパッケージ (FTE と呼ばれています) が追加されていますが、縦書きやマルチカラムといった新しい機能を利用するには、この FTE の API を使うことになります。

ところが、FTE は低レベルの API なので、これを使いこなすには新しいテキストエンジンの構造をきちんと理解する必要があります。つまり、 かるく試してみるにはちょっと敷居が高いのです。

そのため、もう少し簡単にテキストフォーマットを記述するための新しいマークアップ言語が提供される予定です。

Labs に公開されているデモ (Flash Player 10 Feature Demos and Videos@Labs) から新しいマークアップ言語 (Vellum というコードネームで呼ばれています) のサンプルコードをダウンロードすることが可能です。デモコンテンツの "NEW TEXT ENGINE" ボタンをクリックして表示されたコンテンツの下部にある "DOWNLOAD SOURCE" をクリックすると as ファイルを含んだ zip ファイルをダウンロードできます。

zip ファイルを開くと NewTextEngine.as というファイルがあるので、中を見ると以下のような記述が見つかると思います。

<XFL xmlns:flow='http://vellum/prerelease'>
  <flow:TextFlow columnCount='2'>
    <flow:p fontFamily='Helvetica' fontSize='12' color='0xFFFFFF' marginLeft ="10" marginRight="10">
      <flow:span>Layout and style text with tables, inline images and multi-column flow by using components that are compatible with both Flex and Flash while getting the benefits of the new text engine.</flow:span>
    </flow:p>
    <flow:p fontFamily='Helvetica' color='0xFFFFFF' fontSize='12' marginLeft ="10" marginRight="10" marginTop="12">
      <flow:span>The text in these two columns is dynamic, selectable, and editable!</flow:span>
    </flow:p>
  </flow:TextFlow>
</XFL>
 
 

名前空間が http://vellum/prerelease であることから分かるように、まだ詳細な仕様が確定している訳ではありません。 このサンプルのポイントは、具体的な仕様を紹介することではなく、上のようなマークアップ言語が新しく提供される事を伝えることだと思われます。

もちろん、FTE が公開されているわけですから、このマークアップ言語以外にもいいアイデアがあったらどんどんオリジナルのテキストコンポーネントを開発することができますね。

Posted by ackie at 07:27 PM | Comments (0)

July 12, 2007

== 演算子と暗黙の型変換

前回の続きです。なぜ "" == foo が true と評価されるのか、まず、暗黙の型変換が行われる際のルールを一つ確認します。

暗黙の型変換の基準

暗黙の型変換が行われる際の基準となる型は、変数に関連付けられた型注釈ではなく、値であるオブジェクト自体の型です。ですので変数の型に注目していても暗黙の型変換の結果は分かりません。

例えば、今回問題になっている例 foo:Object = 0 では、値である 0 の型が暗黙の型変換の基準になります。foo の型ではありません。そのため if 文の条件は文字列とと数値の比較として扱われます。

var foo:Object = 0;
if ("" == foo) { // 実際には "" == 0 が評価される
  trace("foo is null String");
}
 

そうすると、今回のケースでは "" と 0 が等価であるかどうかが評価されていることになります。

== 実行時の暗黙の型変換のルール

さて、文字列と数値を == で評価する際には、「文字列を数値に変換してから比較する」というルールが適用されます。そのため、まず空文字列から数値への変換が行われます。

AS3 では数値表現として扱うことのできない文字列を数値に変換した場合の結果は 0 になります。空文字列も意味の有る数値表現ではありませんから数値に型変換されたときの値は 0 です。すると "" == 0 は 0 == 0 となります。これにより if 文は最終的に true になるわけです。

別の例を見てみましょう。今度は空の配列と空文字列を比較するケースです。

var foo:Object = [];
if ("" == foo) {
  trace("foo は空文字列です");
}
 

こちらのケースも ”foo は空文字列です” が表示されます。

ここで適用されたルールは、「文字列と配列を == で評価する場合は配列を文字列に変換してから行う」というものです。空の配列を String 型に変換すると空文字列になるため、上の if 文の条件は "" == "" となり、結果として true になったと考えることができます。

ここまでのまとめ

ここで一旦まとめてみましょう。まず、== 演算子では比較対照のデータ型が異なる場合、暗黙の型変換が行われます。そして、変換のルールは型の組み合わせに応じていくつかのルールの中から選ばれます。

ECMAScript の定義を参照すると == 演算子を実行する前に、適用される暗黙の型変換ルールを決定する 23 ステップ相当の条件チェックが必要であることが分かります。従って == 演算子の結果を正しく予測するには、この 23 ステップを適用した場合に起こりうる全てのケースについて考慮する必要があります。さらに、それぞれのケースについて、「空文字列は 0 に変換される」とか「空の配列は空文字列に変換される」といったルールを全て把握していなければなりません。

これはあまり現実的とはいえませんよね。ということで、ここまでのまとめとしては、

  • == 演算子の結果は信用できない

ということになります。

少し AS3 をかばっておくと、これは ECMAScript に準拠したために起きている状況で、AS3 の設計者がみんなを混乱させてやろうとか思った訳では (決して) ありません。

ともあれ、== を使わずにプログラムを書くのも難しいので、プログラムを記述する側で何らかの対応をすることが必要になります。次はその対応方法を考えてみたいと思います。

Posted by ackie at 06:48 PM | Comments (0)

July 09, 2007

AS3 の変数の型に関連する話

今回は AS3 の型システムの特徴についての話です。strict モードの人には関係ない話が続きましたが、ここからはコンパイラの環境設定に依存しない話題です(たぶん)。

まず、下のコードについて考えてみます。 オブジェクト型の変数 foo に数値の 0 を代入して空文字列と比較しています。

var foo:Object = 0;
if ("" == foo) {
  trace("foo は空文字列です");
}
 

上の If 文は空文字を検出するためのものに見えますが、実際にはこれを実行すると ”foo は空文字列です” が表示されます。

この結果は予想通りだったでしょうか?想定外だった人はこの先を。

上記の結果は、AS3 が弱い型の性質を持っているために起こります。AS3 では ’+’ 等のオペレータに対して複数のデータ型が混在すると処理の前に暗黙の型変換が行われます。これによって例えば trace("count:" + 0) のように文字列と数値を”足して”もそれらしい結果を得ることができるわけです。

暗黙の型変換は上の trace() の例のようにしばしば便利な機能として使われますが、一方で予期せぬ結果になるケースもある面倒なものだったりもします。例えば、この記事の最初に挙げた例も暗黙の型変換の結果によるものですが、実際にどのような変換が行われて true と評価されたのかは直感的には理解し難いものです。

ということで、これから上のサンプル内の if 文で実際に起きていたこと、このような結果を避ける方法について話を移そうと思いますが、その前に 2 つほど基礎となる項目の確認です。

int がオブジェクトであること

とりあえず、下のサンプルをみてください。

var foo:int = 0;
var bar:String = foo.toString();
 

2 行目のように int 型の変数のメソッドが呼びだせるということは、AS3 の int は Java でいうと Integer に近いものだということです。ASDoc を見てみると、実際に int が Object のサブクラスであることが確認できます。同様に uint や Number も Object のサブクラスです (Number はクラス名っぽいですね)。

このため冒頭の例でも Object 型の変数に数値を代入することができたわけです。

ちなみに下の 2 行はプログラムの意味的には同じになります。もちろん動的な属性を持たない int 型のオブジェクトのコンストラクタをわざわざ呼ぶ必要はありませんが。

var i:int = 0;
var i:int = new int(0);
 

「型が無い」という型

数値もオブジェクトとして扱われるということで全ての値は Object 型に属するように思えますが、実際は例外があります。Object は undefined を値として持つことができません。undefined が属性値として必要な場合は * を型として指定します。

AS2 では Object 型オブジェクトの初期値は undefind でしたが AS3 では null になっています。* 型の初期値は undefined です。

ところで * は型指定が無いという型(ちょっと矛盾?)です。というわけで、* は型注釈を省略したときに使われる型でもあります。

// 下の2行は同じ意味
var foo;
var foo:*;
 

変数の型を明示的にしない場合 Object 型ではないことに注意しましょう。* 型のオブジェクトは属性やメソッドを持たないため、コンパイル時の型チェックが行われません。

実行時まで型チェックを遅らせられることは動的言語の利点ですが、厳密な型チェックを行う strict モードでも * を使うとこの恩恵にあずかることができます。

ちょっと長くなったので、続きは次回に。

Posted by ackie at 05:46 PM | Comments (0)

July 06, 2007

AS3 での変数宣言とパフォーマンス

前回に引き続き変数宣言の有無による違いを少し追求してみたいと思います。今回はパフォーマンスの違いについてです。よく知られている話だとは思いますがいちおう。

変数宣言の有無とパフォーマンス

まずは、以下のコードを考えます。Date のオブジェクトを生成して getTime() メソッドを 10 万回呼び出しています。変数宣言は行われていません。

date = new Date()
for (i=0; i<100000; i++)
  date.getTime()
 

手持ちの環境で上のコードを実行してみます。すると、for ループの箇所の実行に約 470ms かかりました。

これを下のように変えてみます。2 行目で var を使った変数宣言を追加しています。それから for ループ内では、2 行目で新しく追加した変数経由でメソッドを呼び出しています。

date = new Date()
var dateVar = date  // 追加
for (i=0; i<100000; i++)
  dateVar.getTime()
 

こちらはループの実行に約 340ms かかりました。処理時間が 130ms 程短縮されています。

for ループ内で参照しているオブジェクトは、どちらのケースも同じく Date のインスタンスです。両者の違いは、参照に使用している変数の宣言に var が付いているかどうかだけです。このことから var 無しの変数からオブジェクトを参照する行為には、ずいぶん余計な時間がかかっていることが分かります。

関数内で var を付けて宣言した変数をローカル変数といいます。上の結果が示すようにこローカル変数へのアクセスは高速です。また、ローカル変数の有効範囲は宣言された関数内のみですので、参照の消し忘れによるメモリリークの心配もありません。

一方、var 無しで変数を使用した場合は、前回見たように、クラス内で有効な一種のグローバル変数になります。上の結果のとおり、グローバル変数へのアクセスはローカル変数より時間がかかります。

型注釈の有無とパフォーマンス

次は、変数宣言に型注釈をつけてみます。2 行目の宣言で明示的に Date 型を指定しています。

date = new Date()
var dateTyped:Date = date  // 型を指定
for (i=0; i<100000; i++)
  dateTyped.getTime()
 

実行してみるとループの箇所に約 300ms かかりました。先程の 340ms と比べ更に一割ほど短縮されています。

これは、AS3 の実行環境が型情報をパフォーマンス向上に役立てる機能を持っているからです。また、型注釈をつけるとメモリの使用量を減らす効果もあります。このような実行環境による型情報のサポートは AS3 の大きな特徴です。(注:この箇所は dynamic なクラスのオブジェクトに動的に追加されたメソッドやプロパティには当てはまりません)

変数宣言の追加とここまでのまとめ

さて、よく見ると、ループ内には i という変数も使われています。これもローカル変数として宣言してみます。

date = new Date()
var dateTyped:Date = date
for (var i=0; i<100000; i++)  // i をローカル変数に
  dateTyped.getTime()
 

この結果、約 20ms になりました。i は 2 回使われている分効果が大きいようです。

さらに i に型をつけてみます。

date = new Date()
var dateTyped:Date = date
for (var i:int=0; i<100000; i++)  // i を int 型に
  dateTyped.getTime()
 

これで約 13ms になりました。最初の数値である 470ms と比べて 3% 以下になっています。

var とか型とか書くのを習慣にしたほうがよさそう、という結論になりそうですが、10 万回繰り返してやっとこの程度の差という見方もできます。ここでの例のように後から変数宣言を追加すればよい話ですし、パフォーマンスの観点からだけなら、あまり神経質にならなくてもよいかもしれません。特に、型注釈の有り無しによる差はほとんど無いケースも多かったりします。

ただ、前回説明したように、グローバル変数 (var を使った宣言無しの変数) の使用はプログラムの構造を分かりにくくする可能性があります。パフォーマンスの件と併せると、特に理由の無い限り避けた方が賢明そうですね。

そうすると、ここまでのまとめとしては、

  • var は基本的に付ける
  • 型注釈はあったほうがまし

ということになります。

が、型注釈が無いことでプログラムの実行結果が非常に分かりにくくなる場合があったりします。次はこの点について掘り下げてみようと思います。

Posted by ackie at 06:25 PM | Comments (0)

July 05, 2007

AS3 での変数宣言 (var) の省略について

ちょっと前の記事 (AS3 で動的言語のススメ) に書いたように strict オプションを指定しないことで AS3 の記述の自由度は格段に上がります。とはいえ、”With great power comes great responsibility” の言葉どおり、選択肢が増えた分だけ、コードを記述する側の責任が大きくなることは意識しておく必要があります。

手抜きの書き方を勧めたことに少し責任を感じて、今回は var による変数宣言の有無による変数の振舞いの違いに少し触れてみたいと思います。

var 無し変数の初期化

Flash CS3 でも Flex でも AS3 コンパイラの厳密な型チェックのオプションをオフにすれば var 宣言無しで変数が使用できるようになります。例えば下のコードは問題なくコンパイルできます。

public class Foo
{
  public function setMyGlobal(val:int):void
  {
    i = val;
  }
  public function printMyGlobal():void
  {
    trace(i);
  }
}
 

以下のコードを試してみると 10 が出力されます。ちゃんと動作していることが確認できます。

var foo:Foo = new Foo();
foo.setMyGlobal(10);
foo.printMyGlobal(); // 10 が出力される
 

ところが、setMyGlobal() を呼び出さずに printMyGlobal() を呼び出すと、実行時に i が定義されていないというエラーメッセージが出力されます。

var foo:Foo = new Foo();
foo.printMyGlobal(); // ランタイムエラー発生
 

このことから setMyGlobal() 内で i の初期化が行われていたことが分かります。

public function setMyGlobal(val:int):void
{
  i = val;  // この行が最初に実行された時点で i が初期化される
}
 

値を代入するコードの実行が、最初の回に限り変数の初期化も兼ねています。つまり、コードの実行される順番に結果が依存するわけです。

var を使った宣言を行わない場合は初期化のタイミングに注意するようにしましょう。明示的に var を使って宣言した変数であればインスタンス生成時に変数の初期化が行われます。

var 無し変数のスコープ

さて、再び最初に定義したクラス Foo を使って次のコードを実行してみましょう。

var foo1:Foo = new Foo();
var foo2:Foo = new Foo();
 
foo1.setMyGlobal(10);
foo2.printMyGlobal(); // 10 が出力される
 

そうすると 10 が出力されると思います。foo1 で設定した値が foo2 から参照できていることになります。つまり var を使わずに使用された変数 i はクラスのインスタンス間で共有されているわけです。

var を使わないで変数を初期化するコードを記述するのは、スタティックイニシャライザを記述するようなものだと考えてもよいでしょう。もし、Foo クラスの定義が i をインスタンス変数として定義し忘れたものだったとしたら、ずいぶんと異なる振舞いを記述していることになります。

Flash CS3 の場合なら、ムービークリップのタイムラインに var を付けずに使用している変数があれば、その値は全てのインスタンスで共有されることになるわけです。

コンパイラで厳密な型チェックを行うように指定すれば var 宣言されていない変数はコンパイル時に発見して通知してくれます。AS3 コンパイラの設定がデフォルトで strict モードになっているのはなるほどというところでしょうか。Flash 8 以前の fla を AS3 に設定しなおした場合は strict オプションは選択されていませんのでご注意を。

Posted by ackie at 05:54 PM | Comments (2)

June 14, 2007

Flash Player の新しいフルスクリーンモード

先日公開された Flash Player アップデート 3 にはハードウェア機能を利用したフルスクリーンモードが追加されています。OS の機能を利用した描画は Flash Player としては初ですのでいろいろな環境で試してみてください。従来のフルスクリーンモードも引き続き使用できます。

ハードウェアスケーリングの使い方

Flash Player 9 アップデート 3 の Stage オブジェクトには fullScreenSourceRect という属性が追加されています。この属性は AS2 と AS3 どちらからでも利用可能です。fullScreenSourceRect により指定された領域はハードウェアのスケーリング機能により描画されるため従来のフルスクリーンモードよりパフォーマンスも画質も格段に改善されます。

ここで簡単なサンプルを見てみましょう。下は、Stage を画面全体に広げるサンプルです。

import flash.geom.*;
function goFullScreen()
{
  Stage["fullScreenSourceRect"] = new Rectangle(0, 0, Stage.width, Stage.height);
  Stage["displayState"] = "fullScreen";
} 
 

Flash CS3 側のクラス定義が更新されるまで Stage.fullScreenSourceRect という記述はできませんのでご注意ください。なお fullScreenSourceRect に指定する領域は Stage の外でも構わないようです。

ビデオをフルスクリーンで表示する場合は、Flash 上でのビデオの大きさをオリジナルの大きさにした状態で行うようにします。Stage 上でもビデオのスケールが変更されているとパフォーマンスに大きな影響があります。

下のサンプルは、video オブジェクトを、明示的に表示する映像の大きさに変更してからフルスクリーンにしています。

import flash.geom.*;
function goFullScreen()
{
  // 現在表示されている状態を保存しておく
  myVideo.savedWidth = myVideo.width;
  myVideo.savedHeight = myVideo.height;
  myVideo.savedSmoothing = myVideo.smoothing;
  myVideo.savedDeblocking = myVideo.deblocking;
 
  // 表示する映像のサイズを video オブジェクトに設定
  myVideo.width = myVideo.videoWidth;
  myVideo.height = myVideo.videoHeight;
  myVideo.smoothing = false;
  myVideo.deblocking = 0;
 
  // 映像の領域をフルサイズに
  Stage["fullScreenSourceRect"] = new Rectangle(myVideo.x, myVideo.y, myVideo.width, myVideo.height);
  Stage["displayState"] = "fullScreen";
}
 

ビデオをフルスクリーン表示する場合は、上のようにビデオ以外が表示されないように領域を設定します。また、smoothing を false にして deblocking をオートにするのもパフォーマンス上大事な点になります。

AS3 での注意点

新しいフルスクリーンモードを AS3 で使うには playerglobal.swc を置き換える必要があります。 新しい playerglobal.swc は下のファイルに含まれています。

full_screen_demo.zip (zip, 49.2 MB)

playerglobal.swc を取り出したら以下の場所にコピーしてください。

Flash CS3: Adobe Flash CS3\en\Configuration\ActionScript 3.0\Classes\

Flex Builder: Flex Builder 2\Flex SDK 2\frameworks\libs

Posted by ackie at 05:05 PM | Comments (6)

May 28, 2007

AS3 で動的言語のススメ

AS3 は元来動的言語ですので、改めて勧めるというのも変な感じですが、今回はさらっと読んでいただければと思います。

AS2 と AS3 のコンパイル時の型チェック

まずは何も属性もメソッドも定義されていないクラスを定義してみます。

class Foo { }
 

次に、上のクラスを使って以下のような記述をします。2 行目で存在しないメソッドを呼び出しています。

var foo:Foo = new Foo();
foo.nonExistingMethod();  // 存在しないメソッドの呼び出し
 

このコードをコンパイルしたとき、AS2 ではコンパイル時にエラーが出力されます。一方 AS3 ではコンパイルエラーにはなりません。この結果から、AS3 のほうがコンパイル時のチェックがゆるい言語だということが分かります。

そんな結果にはならなかった!という人は strict というオプションを付けてコンパイルをしているからです。

Flash を使っている場合はパブリッシュ設定パネルの AS3 設定パネルに、Flex Builder であればプロジェクトのプロパティパネルのコンパイラの設定パネルを開いてみると、”strict” あるいは ”厳密な型” という言葉が含まれれるラベルの付いたチェックボックスがあると思います。これを見つけたら選択を解除しましょう。この状態が本来の AS3 コンパイラの動作です。

AS2 における型

Flash 5 で ActionScript 1 が導入されて以来、Flash コンテンツへのスクリプトの利用範囲もどんどん広がって、Flash MX の頃にはコードを大量に記述することも多くなっていました。そんな中、ActionScript でも OOP ができるようにと導入されたのが ActionScript 2 です。これで class キーワードも使えるようになりました。

ところで、AS2 は AS1 上のレイヤーとして実装されました。例えば AS2 は AS1 の上に OOP をサポートするためのフレームワークをのっけた環境であると考えてもよいでしょう。

このとき、AS1 の実行環境は型をサポートしていないので、同じ環境で実行される AS2 にも実行時の型サポートが無いという状況が起きています。つまり、AS2 における型の使用は、オーサリング時の ”生産性向上のベストプラクティス” としての位置づけ以上ではないわけです。実際 OOP をまじめに追求するほど AS2 のアプリケーションは遅くなる傾向を持っています。

それと、AS1 / 2 が実行環境を共有していることで、もうひとつの問題があります。それは実行環境がランタイムエラーを通知しないことです。

AS1 の始めの頃はごく補助的な使われ方を主に想定していたためエラー通知機能が省かれたのだと思いますが、AS2 ではこれは無視できない問題です。例えば、コンテンツの再生時にスクリプトの実行に失敗していても、どこかでエラーが起きているようだと気づいたところで状況を知る手段がないため、問題の箇所を特定することはかなり困難です。

そこで、AS2 のコンパイラは静的言語的な動作をするように設計されました。この記事の最初の例のように、存在しないメソッド呼び出しをコンパイル時にエラーとみなすのはそのためです。

一方、AS3 はデバッグプレーヤがランタイムエラーを報告します。この機能があるため、(ようやく) 動的言語らしく、様々なコンパイル時のチェックをオプションにすることができたのです。

動的言語と静的言語

ちょと聞きかじったところによると、静的言語がコンパイル時に決定できることはできる限り決めてしまえという考え方の言語であるのに対し、動的言語は実行時まで遅らせられることはできるだけ遅らせてみようという言語だということで、一般的には、静的言語はパフォーマンスや大規模開発における生産性に優れていて、動的言語は柔軟性や記述の簡潔さで勝っていると言われているようです。

とはいえ、動的言語と静的言語の間に厳密な線を引くのは難しく、この説明も、動的言語的な性質と静的言語的な性質の記述と理解したほうが適切かもしれません。例えば AS3 のコンパイラは strict オプションを付けることで静的言語的な振舞いを取るようになります。

AS3 も動的言語のはしくれですから、変数宣言 / 型注釈 / セミコロン等なしでプログラムの記述ができます。

foo = ["one", "two", "three"]
for each (i in foo)
  trace(i)
 

for each 構文も AS2 にはなかったですね。

おわりに

AS3 は動的言語なりに柔軟性の高い言語です。問題は Flash CS3 のビジュアルオーサリング環境との統合がまだ最初のステップであること。これは CS4 に向けての大きな課題だと思いますし、是非今後フィードバックをお願いします。

Posted by ackie at 06:57 PM | Comments (0)

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)

June 15, 2006

継承

おくればせながら、継承に関する話題を少しまとめます。

extends

サブクラスを定義するには extends キーワードを使います。

// 親クラスの定義
public class MyBase 
{
  public function hello():String {
    return "hello";
  }
}
// サブクラスの定義
public class MySub extends MyBase
{
}

サブクラスのオブジェクトは親クラスのオブジェクトと同様に使うことができます。

// MyBase 型の変数に代入
var base:MyBase = new MyBase();
var sub:MyBase = new MySub();
// MyBase 型の引数を持つ関数にわたす
needBase(sub);
needBase(base);
 
public function needBase(obj:MyBase):void {
  trace(obj.hello());
}

逆に親クラスはサブクラスの代わりにはなりません、が、下のコードは -strict オプションを付けなければ実行できます。

var sub:MyBase = new MySub();
needSub(sub);
 
public function needSub(obj:MySub):void {
  trace(obj.hello());
}

アクセス制限

プロパティの宣言に private を指定すると、他のクラスからは参照できなくなります。また、public を指定した場合は、どのクラスからも参照できます。

protected を指定したプロパティは、継承したクラスからは参照ができます。デフォルトである internal の場合は、同じパッケージ内のクラスであれば参照できます。

少し注意が必要なのは、protected の場合、他のパッケージのクラスであってもサブクラスであれば参照可能なことです。逆に、protected のプロパティは同じパッケージ内のクラスでもサブクラスでなければ参照できません。

また、internal のプロパティは同じパッケージ内のサブクラスのみにしか継承されません。

ちょっと例を見てみましょう。まず、foo というパッケージに FooBase クラスを定義します。クラス内には protected と internal の2種類の関数を定義します。

package foo
{
  public class FooBase
  {
    protected function protectedFunc():void {
    }
    internal function internalFunc():void {
    }
  }
}

次に、bar というパッケージに上記の FooBase のサブクラスを定義します。関数をオーバーライドするため override キーワードを使っています。

package bar
{
  import foo.FooBase;
  
  public class BarSub extends FooBase
  {
    // 親クラスの定義をオーバーライド
    override protected function protectedFunc():void {
    }
    // こっちはオーバーライドできないはず
    override internal function internalFunc():void {
    }
  }
}

これをコンパイルしてみると、protectedFunc() は通りますが、internalFunc() の方はそんな関数は見つからないといわれてしまいます。

また、サブクラス内のコードで、直接親クラスの protected プロパティを参照することはできません。

public class BarSub extends FooBase
{
  public function callProtected():void {
    var foo:FooBase = new FooBase();
    // protected のメソッドを呼び出してみる
    foo.protectedFunc();
  }
}

上記の例の場合、callProtected() が呼ばれるとエラーが発生します。

Posted by ackie at 06:10 PM | Comments (0)

June 07, 2006

URI のエンコードとデコード

引き続きエンコード関連の関数を紹介します。今回は URI をエンコードするための関数です。

encodeURI() 関数と decodeURI() 関数

encodeURI() 関数 (livedocs@lab) は URI 文字列を UTF-8 値にエンコードします。英数字は変換の対象外ですが、他にも URI の一部として使われる記号は変換されません。

var str:String = "http://w.c/a?b+ま&";
trace(encodeURI(str)); // http://w.c/a?b+%E3%81%BE& が出力される

この例では「ま」だけが変換されていますが、/ 等の文字は変換されていませんね。

encodeURI() によって変換されない記号は以下のものがあります。上段は URI の区切り文字として使用される記号です。

; / ? : @ & = + $ , # 
- _ . ! ~ ' ( )

encodeURI() でエンコードされた文字列は decodeURI() 関数 (livedocs@lab) を使って元に戻すことができます。

var str:String = encodeURI("http://w.c/a?b+ま&");
trace(str); // http://w.c/a?b+%E3%81%BE& が出力される
trace(decodeURI(str)); // http://w.c/a?b+ま& が出力される

encodeURIComponent() 関数と decodeURIComponent() 関数

encodeURIComponent() 関数 (livedocs@lab) は encodeURI() とほぼ同じような関数ですが、encodeURI() が URI 全体をエンコードするためのものであるのに対し、encodeURIComponent() は URI コンポーネントと呼ばれる URI の一部分をエンコードするための関数です。

URI コンポーネントとは ":", "/", ";" 等の記号で区切られた文字列です。例えば http や www.adobe.com は URI コンポーネントです。

実際に encodeURI() と encodeURIComponent() でエンコードした結果を比べると以下のようになります。どこが違うか分かりますか?

var str:String = "http://w.c/a?b+ま&";
trace(encodeURI(str)); // http://w.c/a?b+%E3%81%BE& が出力される
trace(encodeURIComponent(str)); // http%3A%2F%2Fw.c%2Fa%3Fb%2B%E3%81%BE%26 が出力される

encodeURIComponent() では ":" や "/" もエンコードの対象になっています。これは encodeURIComponent() が URI の区切りに使われる記号を特別扱いしないからです。

英字と数字以外で encodeURIComponent() がエンコードしない文字は以下のとおりです。

- _ . ! ~ ' ( )

encodeURIComponent() でエンコードされた文字列は decodeURIComponent() 関数 (livedocs@lab) で元に戻します。

var str:String = encodeURIComponent("http://w.c/a?b+ま&");
trace(str); // http%3A%2F%2Fw.c%2Fa%3Fb%2B%E3%81%BE%26 が出力される
trace(decodeURIComponent(str)); // http://w.c/a?b+ま& が出力される

Posted by ackie at 06:14 PM | Comments (0)

June 06, 2006

文字列のエスケープ

escape() 関数と unescape() 関数

escape() 関数は (livedocs@lab) は文字列を URL エンコードのフォーマットに変換するグローバル関数です。基本的に、英数字以外の文字であれば % 付の16進数に変換されます。+ などいくつか変換されない文字もあります。

trace(escape("1+3%2=0")); // 1+3%252%3D0 が出力される
trace(escape("h:m@u/a?b&c")); // h%3Am@u/a%3Fb%26c が出力される
trace(escape("ハロー")); // %u30CF%u30ED%u30FC が出力される

escape() で変換された文字を戻すには unescape() 関数 (livedocs@lab) を使います。

var str:String = escape("1+3%2=0");
trace(str); // 1+3%252%3D0 が出力される
trace(unescape(str)); // 1+3%2=0 が出力される

escapeMultiByte() 関数と unescapeMultiByte() 関数

escapeMultiByte() 関数 (livedocs@lab) と unescapeMultiByte() 関数 (livedocs@lab) は flash.utils パッケージに含まれる関数です。

escapeMultiByte() は文字列を UTF-8 もしくはシステムのコードページ (Shift-JIS 等) に変換した文字列を返します。

どちらのエンコーディングになるかは System.useCodePage の値で決まります。true の場合は OS のコードページ、false の場合は UTF-8 です。

var str:String = "もなか";
// 比較のために URL エンコーディングの結果を確認
trace(escape(str)); // %u3082%u306A%u304B が出力される
// OS のコードページ(SJIS)にエンコード
System.useCodePage = true;
trace(escapeMultiByte(str)); // %82%E0%82%C8%82%A9 が出力される
// UTF-8 にエンコード
System.useCodePage = false;
trace(escapeMultiByte(str)); // %E3%82%82%E3%81%AA%E3%81%8B が出力される

変換した文字列を戻すには unescapeMultiByte() 関数を使用します。

var str:String = escapeMultiByte("もなか");
trace(str); // %82%E0%82%C8%82%A9 が出力される
trace(unescapeMultiByte(str)); // もなか が出力される

unescapeMultiByte() も escapeMultiByte() と同様に System.useCodePage の値により結果が変わります。unescapeMultiByte() で元の文字列に戻す際は escapeMultiByte() で変換した際と System.useCodePage の値が同じである必要があります。自動的に UTF-8 かどうかを判定してくれたりはしません。

また、 System.useCodePage=true で変換した場合の結果はシステムのコードページに依存します。そのため、エンコードとデコードが同じコードページのシステム上で行われないと元の文字列に戻せません。例えば Shift-JIS で変換した文字列はShift-JIS の環境でしか戻せなくなります。

Posted by ackie at 07:17 PM | Comments (0)

June 01, 2006

NaN と Infinity

NaN と Infinity

Number 型は浮動小数点数以外に3つの値を持ちます。

最初の2つは Infinity と -Infinity です。Infinity は正の無限大の意味で Number.POSITIVE_INFINITY として定義されています。Number.MAX_VALUE の範囲を超えた値はこれになります。同様に-Infinity は負の無限大の意味で Number.NEGATIVE_INFINITY として定義されています。こっちは -Number.MAX_VALUE よりも小さい値ということになります。

3つ目の値は NaN で、Number ではない値という値です。Number.NaN でも使えます。

これら3つの値は、それぞれ以下のような場合に使われます。

trace(1 / 0); // Infinity が出力される
trace(-1 / 0); // -Infinity が出力される
trace(0 / 0); // NaN が出力される
trace(Math.sqrt(-1)); // NaN が出力される

最後の例では負の数の平方根を求めようとしているため結果が NaN になっています。(解が実数でないため)

ところで、NaN は条件文で比較すると結果が必ず false になります。

trace(1/0 == Infinity); // true が出力される
trace(0/0 == NaN); // false が出力される
trace(NaN == NaN); // false が出力される

そのために isNaN() というグローバル関数が用意されています。NaN かどうかを調べる際にはこれを使います。

trace(isNaN(0/0)); // true が出力される

同様に Infinity や -Infinity を調べるための isFinite() 関数もあります。

trace(isFinite(1/1)); // true が出力される
trace(isFinite(0/1)); // false が出力される
trace(Infinity == Infinity); // true が出力される
trace(Infinity == -Infinity); // false が出力される

Infinity と -Infinity はちゃんと区別されます。

Posted by ackie at 05:50 PM | Comments (0)

May 31, 2006

Number

Number (livedocs@lab) は整数や浮動小数点数を表すことのできる型です。Number のオブジェクトは IEEE-754 に定義される 64-bit の倍精度のフォーマットで約 4.94e-324 から 1.80e+308 の値を正負それぞれの領域で表すことができます。このため int や uint ではカバーできない数値も扱うことができます。

正確な最大値/最小値は以下のとおり定義されています。

Number.MAX_VALUE // 正の数の最大値
Number.MIN_VALUE // 正の数の最小値

Number オブジェクトは toString() メソッドを使って文字列に変換できます。Number クラスでも引数に基数を指定することができます。が、指定した場合は小数部が無視されます。

var myNum:Number = 9.5;
trace(myNum.toString()); // 9.5 が出力される
trace(myNum.toString(2)); // 1001 が出力される
trace(myNum.toString(16)); // 9 が出力される

toFixed() メソッド

toFixed() メソッドは Number オブジェクトの持つ値を小数点数で表現した文字列として返します。引数には小数点以下の有効な桁数を指定します。指定できる範囲は 0 から 20 です。

var num1:Number = 7.31843;
trace(num1.toFixed(2)); // 7.32 が出力される
var num2:Number = 4;
trace(num2.toFixed(2)); // 4.00 が出力される

指定された桁数分の小数部が無い値には必要な数だけ 0 が追加されます。

toExponential() メソッド

toExponential() メソッドは Number オブジェクトの持つ値を指数形式で表した文字列として返します。toFixed() と同じように引数には小数点以下の有効な桁数を指定します。指定できる範囲はこっちも 0 から 20 です。

var num1:Number = 731843;
trace(num1.toExponential(2)); // 7.31e+5 が出力される
var num2:Number = 4;
trace(num2.toExponential(2)); // 4.00 が出力される
var num1:Number = 0.731843;
trace(num1.toExponential(2)); // 7.31e-1 が出力される

0 から 10 の間の数値には e+0 とかがついたりはしないようです。

toPrecision() メソッド

toPrecision() メソッドは Number オブジェクトの持つ値を指定した有効桁数の文字列に変換します。戻り値は小数の場合も指数形式の場合もあります。引数の範囲は 1 から 21 です。

var num1:Number = 73.1843;
trace(num1.toPrecision(3)); // 73.1 が出力される
var num2:Number = 4000;
trace(num2.toPrecision(3)); // 4.00e+3 が出力される

Number() 関数

Number() (livedocs@lab)はプリミティブ型の値を浮動小数点数に変換するグローバル関数です。

trace(Number("")); // 0 が出力される
trace(Number(" 2.4")); // 2.4 が出力される
trace(Number("2.a")); // NaN が出力される

int() や uint() と違い数字で無い文字を含む文字列は NaN になります。

Posted by ackie at 06:23 PM | Comments (0)

May 30, 2006

int と uint

int 型と uint 型

int (livedocs@lab) と uint (livedocs@lab) はそれぞれ 32 ビットの符号付/符号無し整数を表す型です。

表現できる範囲は int が -2,147,483,648 から 2,147,483,647、uint が 0 から 4,294,967,295 です。この範囲で足りない場合には Number を使うことになります。int/uint を使用したほうが Number よりも演算速度は速いため int/uint で用が足りる場合はこちらを選ぶのがよさそうです。

さて、int/uint それぞれの最大値と最小値は以下のように定義されています。

int.MAX_VALUE // int の最大値
int.MIN_VALUE // int の最小値
uint.MAX_VALUE // uint の最大値
uint.MIN_VALUE // uint の最小値

この記述の仕方からも推測できるように int/uint ともに小文字で始まっていますがクラスです。そのため以下のような記述も可能です。

var myInt:int = 1234;
trace(myInt.toString()); // 1234 が出力される
// さすがに以下の記述はむり
// trace(1234.toString());  コンパイルエラー

toString() メソッドには引数に文字列変換する際の基数を指定することもできます。基数の範囲は 2 から 32 です。

var myUint:uint = 9;
trace(myUint.toString(2)); // 1001 が出力される
trace(myUint.toString(8)); // 11 が出力される

コンストラクタを使用てオブジェクトを生成したり、オブジェクトからプリミティブ値(ちょっと定義が変ですが)を取り出したりすることもできます。

var num:int = new int(2);
trace(num.valueOf()); // 2 が出力される

RGBA 値(RGB とアルファ値)を指定する場合には uint を使う必要があります。AS2 に慣れた方はご注意を。

int() 関数 / uint() 関数

int() (livedocs@lab) と uint() (livedocs@lab) はプリミティブ型(Boolean、null、Number、String、undefined)の値を符号付または符号無しの整数に変換するグローバル関数です。

trace(int(1.2)); // 1 が出力される
trace(int(true)); // 1 が出力される
trace(int(false)); // 0 が出力される
trace(int("")); // 0 が出力される
trace(int(" 2")); // 2 が出力される
trace(int("2a")); // 0 が出力される

数字以外の文字を含む文字列は 0 になります。

parseInt() 関数

上記の int() を使っても文字列を int に変換できましたが、parseInt() 関数 (livedocs@lab) を使うと変換時の基数を指定することができます。基数は省略可能です。

trace(parseInt("100")); // 100 が出力される
trace(parseInt("100", 16)); // 256 が出力される
trace(parseInt("100X", 16)); // 256 が出力される

parseInt() は引数の文字列が数字で始まっていれば、変換できるところまで変換した値を返します。

0x で始まる数値は16進数として扱われます。AS2 と違い 0 で始まる数値は8進数として扱われません。

trace(parseInt("0x100")); // 256 が出力される
trace(parseInt("0100")); // 100 が出力される

parseFloat() 関数

浮動小数点数が必要な場合は parseFloat() 関数 (livedocs@lab) を使用します。

trace(parseFloat("100.1")); // 100.1 が出力される

こちらは基数を指定したり 0x で始まる文字列を使うことはできません。

Posted by ackie at 08:20 PM | Comments (0)

May 18, 2006

describeType メソッド

describeType() メソッド (livedocs@lab) は引数に渡されたオブジェクトの情報を XML 形式で返します。引数には ActionScript の任意のオブジェクトを(クラスや int なども)渡すことができます。

var mc:MovieClip = new MovieClip();
var typeInfo:XML = describeType(mc);

返される XML オブジェクトに含まれる情報は引数として渡されたオブジェクトの属性やメソッド等です。Java のリフレクションと同じような機能です。

XML オブジェクトだと扱いが面倒に思われるかもしれませんが、E4X のおかげでむしろ Object よりも便利です。例えば、上の例の続きで、戻り値が void のメソッドのリストを取得するには typeInfo.method.(@returnType == "void") のように記述できます。

出力例

以下、MovieClip のインスタンスを describeType() に渡した結果を例として具体的な出力を説明します。

まず、ルートノードは type というタグです。type タグの属性を見ると、パッケージ名とクラス名、親クラス名、その他のクラス宣言時の修飾子が分かります。

<type name="flash.display::MovieClip" base="flash.display::Sprite" isDynamic="true" isFinal="false" isStatic="false">  

残りのタグは、type の子ノードです。一つ目は extendsClass タグで、継承関係にある全ての上位クラスの名前とパッケージ名が type 属性から分かります。また implementsInterface タグで継承しているインターフェースも分かります。

<extendsClass type="flash.display::Sprite"/>
<extendsClass type="flash.display::DisplayObjectContainer"/>
<extendsClass type="flash.display::InteractiveObject"/>
<extendsClass type="flash.display::DisplayObject"/>
<extendsClass type="flash.events::EventDispatcher"/>
<extendsClass type="Object"/>
<implementsInterface type="flash.display::IBitmapDrawable"/>
<implementsInterface type="flash.events::IEventDispatcher"/>

属性情報は accessor タグから分かります。名前と、アクセス、型、宣言されたクラスが分かります。

<accessor name="totalFrames" access="readonly" type="int" declaredBy="flash.display::MovieClip"/>
<accessor name="currentFrame" access="readonly" type="int" declaredBy="flash.display::MovieClip"/>

メソッド情報は method タグです。メソッド名、宣言したクラス、戻り値の型が属性に記述されます。パラメータを持つメソッドは子ノードを持ち、そこに、順番、型、オプションかどうかの情報があります。

<method name="play" declaredBy="flash.display::MovieClip" returnType="void"/>
<method name="gotoAndPlay" declaredBy="flash.display::MovieClip" returnType="void">
  <parameter index="1" type="*" optional="false"/>
  <parameter index="2" type="String" optional="true"/>
</method>

一通り MovieClip の定義情報が終わると、上位クラスで宣言されている属性とメソッドが続きます。MovieClip の場合は Sprite から EventDispatcher まで長々と情報が表示されます。

<method name="startDrag" declaredBy="flash.display::Sprite" returnType="void">
<parameter index="1" type="Boolean" optional="true"/>
<parameter index="2" type="flash.geom::Rectangle" optional="true"/>
</method>
...
...
<method name="addEventListener" declaredBy="flash.events::EventDispatcher" returnType="void">
<parameter index="1" type="String" optional="false"/>
<parameter index="2" type="Function" optional="false"/>
<parameter index="3" type="Boolean" optional="true"/>
<parameter index="4" type="int" optional="true"/>
<parameter index="5" type="Boolean" optional="true"/>
</method>

静的情報の取得

上の例ではインスタンスの情報のみで、クラスメソッド等の情報は出てきませんでした。static 宣言されている情報を取得するには describeType() にクラスオブジェクトを渡します。

下の例では Font クラスオブジェクトを渡しています。

trace(describeType(Font));

この例での出力は以下のようになります。type タグの base 属性が Class になっています。その他の属性も、クラスオブジェクトに対するもので Font クラスの属性ではありません。

type タグの直接の子ノードになっている method や accessor タグが static 宣言されたものです。インスタンス変数やメソッドは factory タグの子ノードになっています。

<type name="flash.text::Font" base="Class" isDynamic="true" isFinal="true" isStatic="true">
  <extendsClass type="Class"/>
  <extendsClass type="Object"/>
  <method name="enumerate" declaredBy="flash.display::Font" returnType="Array"/>
  <method name="register" declaredBy="flash.display::Font" returnType="void">
    <parameter index="1" type="Class" optional="false"/>
  </method>
  <accessor name="prototype" access="readonly" type="*" declaredBy="Class"/>
  <factory type="flash.text::Font">
    <extendsClass type="Object"/>
    <accessor name="fontStyle" access="readonly" type="String" declaredBy="flash.text::Font"/>
    <accessor name="fontName" access="readonly" type="String" declaredBy="flash.text::Font"/>
    <method name="hasGlyphs" declaredBy="flash.text::Font" returnType="Boolean">
      <parameter index="1" type="String" optional="false"/>
    </method>
  </factory>
</type>

beta3 ではメタタグ情報が出力されないようで、使用変更というよりはバグっぽい感じです。

Posted by ackie at 06:50 PM | Comments (0)

May 17, 2006

Dictionary クラス(前の続き)

ようやく new Dictionary() が実行できるようになったので続きです。

weak-value ディクショナリー

以前の記事に書いたように、Dictionary クラスは weak-key ディクショナリーの機能を持っています。キーオブジェクトへの参照が弱い参照のため、使われなくなったキーとそのエントリーを自動的に開放するという使い方ができます。

一方、値として管理されているオブジェクトがしばらく参照されなかったら自動的に開放するという使い方はできません。

このようなケースは(本来であれば) weak-value ディクショナリーを使う場面です。weak-value ディクショナリーでは値オブジェクトへの参照が弱い参照のため、他から参照されていない値オブジェクトはガーベッジコレクションの対象になります。

さて、現状で weak-value ディクショナリーを実現するには Dictionary のキーと値の扱いを入れ替えてしまうのが簡単そうです。Proxy を使うと以下の用に実現できます。

public dynamic class MyDictionary extends Proxy {
  private var _dict:Object;
  public function MyDictionary() {
    _dict = new Dictionary(true);
  }
  override flash_proxy function setProperty(name:*, value:*):void {
    // 値とキーを逆にしてエントリーを追加
    _dict[value] = name;
  }
}

本来キーとして使われるオブジェクトを値として格納したので、取り出す際には一つ一つ調べることになります。

override flash_proxy function getProperty(name:*):* {
  for (var value:* in _dict) {
    if (_dict[value] == name) {
      return value;
    }
  }
}

ただこの方法ではエントリーが多くなるに連れ参照時のパフォーマンスが悪くなりそうです。

そこで、ちょっと贅沢ですがエントリーごとに Dictionary を生成するようにしてみます。

override flash_proxy function setProperty(name:*, value:*):void {
  // エントリ専用の Dictionary オブジェクト生成
  var dict4Val:Dictionary = new Dictionary(true);
  // 値とキーを逆にしてエントリーを追加
  dict4Val[value] = name;
  // Dictionary オブジェクトを値として設定
  _dict[name] = dict4Val;
}

今回は Dictionary が一つしかエントリを持たないため、取り出す際は最初に見つかったものをそのまま返すことができます。

override flash_proxy function getProperty(name:*):* {
  // まず対応する Dictionary オブジェクトを取り出す
  var dict4Val:Dictionary = _dict[name];
  // dict4Val は一つしかキーを持たないはず
  for (var value:* in dict4Val) {
    // 見つかった値を返す
    return value;
  }
}

Posted by ackie at 06:18 PM | Comments (0)

May 15, 2006

dynamic クラス

Dreamweaver 8.0.2 アップデータが公開されました。IE のアクティブコンテンツ関連の変更に対応します。ダウンロードはこちらです。(Dreamweaver 8.0.2 アップデータ

さて、遅ればせながら、前の記事のフォローということで。

ダイナミック(dynamic)クラス

AS3 のクラスはコンパイル時に属性が決定されるのが基本ですが、ダイナミッククラスとして宣言すると実行時に属性や関数を定義することができます。

ダイナミッククラスにするにはクラス定義に dynamic 属性を付けます。

public dynamic class FooDynamic {
  private var privProp:String = "private property";
  public var pubProp:String = "public property";
}

上記の FooDynamic のインスタンスを作成すると属性や関数定義を追加することができます。例えば、以下の例では属性と関数を追加しています。

var foo:FooDynamic = new FooDynamic();
foo.dynamicProp = "foo";
foo.dynamicFunc = function():String { return "foo"; };

ダイナミッククラスは動的に属性が追加できるため、コンパイル時に存在しない属性への参照が見つかってもエラーにはなりません。また、実行時でも、存在しない属性への参照は undefined の値となりリファレンスエラーにはなりません。

とはいえ、実行時に存在しない関数を呼び出そうとするとさすがにリファレンスエラーになります。

var foo:FooDynamic = new FooDynamic();
trace(foo.nonProp); // undefined が出力される
foo.nonFunc(); // リファレンスエラーになる

ところで、動的に追加された関数からは private な属性や関数にアクセスすることができません。public な属性や関数へのアクセスは可能ですが、明示的に this またはオブジェクトを指定することが必要です。

var foo:FooDynamic = new FooDynamic();
foo.traceProperties = function():void {
  trace(this.privProp); // undefined が出力される(存在しない属性として扱われる)
  trace(this.pubProp); // public property が出力される
}
foo.traceProperties();

上の例は FooDynamic クラスに traceProperties() メソッドを追加して private/public それぞれの属性を参照しています。

ちなみに、追加する際に private 宣言されている属性や関数と同じ名前を使ってもエラーにはなりません。別の定義として扱われるためです。

Posted by ackie at 09:22 PM | Comments (0)

May 12, 2006

* (アスタリスク)と void と Null

AS3 のオブジェクトは基本的には Object 型(またはそのサブクラス)で表されますが、例外となる型が3つあります。今回はその3つの紹介です。

* (アスタリスク)

* は「型指定なし」という型情報を指定するときに使います。つまり型の指定を省略したときと同じになります。

public var foo:*; // 型指定なし
public var bar;  // こちらも * と同じ扱い

だとすると * なんて付けない方が楽そうですが、何も無いと、こんどは付け忘れたのか本当に型指定を省略したかったのかが分からなくなってしまいます。ですので、きっちりとコードを書くのが趣味の人は * を、型なんてそもそも気にする気の無い人は何も無しを選択するのがよいでしょう。

「型指定なし」で宣言した変数には、コンパイル時に型チェックが起きません(当たり前ですが)。そのため、とりあえずエラーが出るまでは使っちゃおう、というずぼらな使い方ができます。動的言語ならではの使い方ですね。

ただし、実行時には何らかの値をもつわけですから、いずれにせよ実行時には型チェックが行われます。

また、型指定なしにすると Object 型では使用できない値、すなわち undefined を使うことができます。

void

void が持つ値は一つだけで undefined です。void は関数の戻り値を指定する際にしか使用できません。

public function foo():void {
  // return なし
}

戻り値がそもそも存在しないので値が undefined というわけです。

Null

Null が持つ値も一つだけで null です。Null はクラスと関連付けられていないため、変数等の型指定には使用できません。

null は Object 型への参照が値を持たないことを示します。初期化されていない Object 型の変数への参照は null になります。

null と比較すると undefined は値だけでなく型情報もないことを示す値であることが分かると思います。

例えば dynamic クラスで、存在しない属性を参照したときの値も undefined です(この場合も型情報のない点が共通になっています)。このように AS3 のような動的に属性の追加できる言語では null と undefined を区別できることが役立つケースもあるわけです。

Object と Null と void を合わせると AS3 の全ての値を持つことができます。

Posted by ackie at 06:42 PM | Comments (0)

April 20, 2006

Dictionary クラス

Dictionay クラス (livedocs@lab) は beta2 から追加されたクラスで、オブジェクトをキーとして値を管理することができます。Object を使っても同じように連想配列を実現できますが、Dictionary ではいわゆる”弱い参照”が使えます。

また、Object では文字列がキーとして扱われるため toString() の値が同じオブジェクトは全て同じキーとして扱われますが、Dictionary では別々のオブジェクトであれば toString() の値に関わらず異なるキーとして扱われます。

具体的な例を見てみましょう。下の例では key1 と key2 という2つのオブジェクトが同じ文字列に変換されるように設定しています。

// まず2つのキーを生成
var key1:Object = new Object();
var key2:Object = new Object();
key1.toString = function():String { return "key"; };
key2.toString = function():String { return "key"; };
// 値のオブジェクトを生成
var myVal:Object = "my value";
 
// Object の場合
var myKey:Object = new Object();
myObj[key1] = myVal;
trace(myObj[key2]); // my value が出力される
 
// Dictionary の場合
var myDict:Dictionary = new Dictionary();
myDict[key1] = myVal;
trace(myDict[key2]); // undefined が出力される

Object の場合には key1 をキーとして代入した値を key2 で取得できますが、Dictionary の場合には key1 をキーとする値は key1 でしか取り出すことができません。

ところで、beta 2 では Flex Builder 2 のバグのため Dictionary クラス型が参照できません。そのため上記のコードはコンパイルできません...現時点では、このような場合のための(?)getClassByName() メソッドを使用してインスタンスを作成する必要があります。

// beta 2 のバグを回避
var dictionaryCls:Class = getClassByName("flash.util.Dictionary");
var myDict:* = new dictionaryCls(); 
// 本来はこちらの宣言でインスタンスを生成する
//var myDict:Dictionary = new Dictionary();
myDict[key1] = myVal;
trace(myDict[key2]); // undefined が出力される

さて、Dictionary の登録を削除するときは以下の方法が使用できます。それぞれ、値のみを削除する場合とエントリーを削除する場合です。

// 値オブジェクトへの参照を削除
myDict[key1] = null;
trace(myDict[key1]); // null が出力される
// myDict からエントリーを削除
delete myDict[key1];
trace(myDict[key1]); // undefined が出力される

弱い参照の使用

弱い参照とは、たとえ参照があってもガーベッジコレクションの対象となる種類の参照です。キャッシュなど”とりあえず置いておく”タイプのオブジェクトを管理するときなどに便利です。

Dictionary クラスは弱い参照を利用した weak-key と呼ばれるタイプのディクショナリーとして使うことができます。weak-key ディクショナリーでは、キーとして使われているオブジェクトへの参照が弱い参照になっています。そのため、キーオブジェクトへの参照が Dictionary からのみだと、ガーベッジコレクションのタイミングでキーオブジェクトが削除され、併せて Dictionary 内の該当するエントリが削除されます。

Dictionary クラスを weak-key ディクショナリーとして使うには、コンストラクタの引数に true を指定します。デフォルトは false になっています。

// beta 2 のバグを回避
var dictionaryCls:Class = getClassByName("flash.util.Dictionary");
var myDict:* = new dictionaryCls(true); 
// 本来はこちらの宣言でインスタンスを生成する
//var myDict:Dictionary = new Dictionary(true);
// 後の使い方は Object とほぼ同じ
var myKey:Object = new Object();
var myVal:Object = new Object();
myDict[myKey] = myVal;

上の例で myKey が myDict からのみの参照であれば myKey はガーベッジコレクションの対象になります。

Posted by ackie at 06:30 PM | Comments (0)

April 19, 2006

StringBuilder クラス

StringBuilder クラス (livedocs@lab) は String と同じように文字列を扱うクラスですが、特に文字列に追加や挿入などの操作を何回も行うような場合に使います。String と違い、操作の度に新しいオブジェクトを生成したりしないため、メモリをより有効に使用することができます。特に、細かな追加を何回も行う場合には String と比べて高速です。

StringBuilder のインスタンスを生成するにはコンストラクタを使用します。String のように直接文字列で初期化することはできません。

var sb:StringBuilder = new StringBuilder("hello world"); 

capacity 属性と length 属性

StringBuilder はインスタンス生成時に、文字列を保持するための領域をメモリ上に確保します。 領域の大きさは capacity 属性(読み取り専用)から取得することができます。文字列自体の長さは length 属性から取得できます。

var sb:StringBuilder = new StringBuilder("hello world"); 
trace(sb.length); // 11 が出力される
trace(sb.capacity); // 16 が出力される

上記のように StringBuffer は legth 以上の大きさになるようなあるまとまった単位でメモリを確保します。このため余っている領域で足りる程度の追加/挿入であればメモリを確保しなくてもよいのです。

余談(?)ですが、length は設定可能です。length を小さくすると文字列の長さも短くなります。一旦短くなった文字列は length のみ大きくしても元には戻りません。

var sb1:StringBuilder = new StringBuilder("hello world");
sb1.length = 3;
trace(sb1); // hel が出力される
sb1.length = 110;
trace(sb1); // hel が出力される
trace(sb1.capacity); // 142 が出力される

length に応じて capacity は大きくなりますが capacity を確保することが目的であれば専用のメソッド(後述)を使用するほうがよいでしょう。

append() メソッド、 insert() メソッド、 remove() メソッド

最初の例に続けて、少し文字列を付け足して見ます。追加するには append() メソッドを使用します。

sb.append(" wide web");
trace(sb); // hello world wide web が出力される
trace(sb.length); // 20 が出力される
trace(sb.capacity); // 34 が出力される

文字列が確保されていた領域より長くなったため capacity が大きくなっています。大体倍になっていますね。

append() は複数の引数を取ることができます。2つ以上指定した場合は、引数の順に追加されます。

文字列の途中に文字列を挿入するには insert() メソッドを使用します。第一引数が挿入位置のインデックス、第二引数が挿入する文字列です。

上の例に続けて文字列を挿入してみます。

sb.insert(5, " beautiful");
trace(sb); // hello beautiful world wide web が出力される
trace(sb.length); // 30 が出力される
trace(sb.capacity); // 34 が出力される

今回は capacity は追加することなく処理ができました。

remove() メソッドを使うと文字列の一部削除ができます。第一引数が削除開始するインデックス、第二引数が削除終了位置の次のインデックスです。

sb.remove(0, 6);
trace(sb); // beautiful world wide web が出力される
trace(sb.length); // 24 が出力される
trace(sb.capacity); // 34 が出力される

ensureCapacity() メソッドと trimToSize() メソッド

StringBuilder のインスタンスの持つ capacity を明示的に確保したい場合は ensureCapacity() メソッドを使います。事前に必要となる大きさが分かっている場合は、ちょっとずつ増やすよりは効率がよさそうです。

var sb2:StringBuilder = new StringBuilder();
sb2.ensureCapacity(1000);
trace(sb2.length); // 0 が出力される
trace(sb2.capacity); // 1000 が出力される

逆に不要な領域を開放するには trimToSize() メソッドを使います。上の例の続きです。

sb2.append("what a wonderful world");
trace(sb2.length); // 22 が出力される
trace(sb2.capacity); // 1000 が出力される
sb2.trimToSize();
trace(sb2.length); // 22 が出力される
trace(sb2.capacity); // 22 が出力される

capacity が文字列の長さまで小さくなっているのが分かります。(16 よりは小さくならないようです)

Posted by ackie at 06:13 PM | Comments (2)

April 13, 2006

Proxy クラス

Proxy クラス (livedocs@lab) は既存のオブジェクトの振る舞いを変えたいときに使います。例えば、Proxy のサブクラスである ObjectProxy クラス (livedocs@lab) は属性が変更されたとき、イベントでそれを知らせることができるようになっています。(注:ObjectProxy は Flex 2 フレームワークのクラスです)

Proxy クラスにはコンストラクタがありません。従って、Proxy クラスを使用する際は、サブクラスを定義しメソッドをオーバーライドするのが基本です。オーバーライドしていないメソッドが呼ばれた場合は例外が投げられます。

属性へのアクセスの実現

さて、Proxy クラス(のサブクラス)のインスタンスの属性を参照するには getProperty メソッドをオーバーライドします。

import flash.util.*;
 
public dynamic class MyProxy extends Proxy {
  private var _obj:Object = new Object();
 
  override flash_proxy function getProperty(name:*):* {
    return _obj[name];
  }
}

Proxy クラスのメソッドの呼ばれ方は少々変わっています。例えばここで定義している getProperty() メソッドが呼ばれるのは Proxy クラスのサブクラスのインスタンスの属性が参照されたときです。

つまり、Proxy のメソッドは普通に直接呼びだすためのものではありません。これらのメソッドには通常の属性へのアクセスと区別するために別の名前空間(flash_proxy)が指定されています。

実際に MyProxy を使用するコードは以下のようになります。

var myProxy:MyProxy = new MyProxy();
trace(myProxy.foo); // MyProxy の getProperty(foo) が呼ばれる。出力は undfined になる。

コーディング上 MyProxy に定義されていない属性(この例では foo)にアクセスすることになるため、上の MyProxy のクラス定義では dynamic なクラスとして宣言しています。(コンパイラの -strict オプションをオフにしても構いません。)

属性操作には、設定/削除/検査もありますね。これらに対応するメソッドも MyProxy に実装してみます。

override flash_proxy function setProperty(name:*, value:*):void {
  _obj[name] = value;
}		
override flash_proxy function deleteProperty(name:*):Boolean {
  return delete _obj[name];
}
override flash_proxy function hasProperty(name:*):Boolean {
  return name in _obj;
}

これで、以下のような操作が可能になります。

var myProxy:MyProxy = new MyProxy();
myProxy.foo = "foo";       // setProperty が呼ばれる
trace(myProxy.foo);        // getProperty が呼ばれる:出力は foo
trace("foo" in myProxy);   // hasProperty が呼ばれる:出力は true
trace(delete myProxy.foo); // deleteProperty が呼ばれる:出力は true

関数の呼び出しの実現

Proxy のサブクラスに対して関数をコールすると callProperty メソッドが呼ばれます。callProperty メソッドの使い方は以下のような感じです。

override flash_proxy function callProperty(name:*, ...rest):* {
  var func:Function = _obj[name] as Function;
  if (func != null) {
    return func.apply(null, rest);
  }
}
 
var myProxy:MyProxy = new MyProxy();
myProxy.bar = function():String { return "bar"; };
trace(myProxy.bar());  // callProperty が呼ばれ、bar が出力される

イテレーションへの対応

for..in や for each..in のようなイテレーションで Proxy を使えるようにするには、まず nextNameIndex() メソッドをオーバーライドします。このメソッドは次の属性のインデックス値を返します。

private var _props:Array; // 属性格納用の配列
 
override flash_proxy function nextNameIndex(index:int):int {
  if (index == 0) { // イテレーションの開始、配列を初期化する
    _props = new Array();
    for (var:* x in _obj) {
      _props.push (x);
    }
  }
  if (index < _props.length) {
    return index + 1;
  } else {
    return 0;
  }
}

for..in を使用するには nextName() メソッドもオーバーライドします。

override flash_proxy function nextName(index:int):String {
  return _props[index-1].toString();; // nextNameIndex が 1 から返すため
}
 
for (var prop:* in myProxy) { 
  trace(prop + " = " + myProxy[prop]); 
}

for each ..in を使用するには nextValue() メソッドをオーバーライドします。

override flash_proxy function nextValue(index:int):* {
  return _obj[_props[index-1]];
}
 
for each (var item:* in myProxy) { 
  trace(item); 
}

Posted by ackie at 08:04 PM | Comments (0)

April 12, 2006

Timer クラス

Timer クラス (livedocs@lab) は一定の間隔毎に処理を行いたい場合に使用します。Timer のインスタンスは、予め設定された時間が経つとイベントをディスパッチします。

Timer クラスは AS2 の setInterval() / getInterval() を置き換えるものです。AS3 でも setInterval() / getInterval() は共に flash.util パッケージ下にあり使用可能ですが、これは既存のコードとの互換性を持たせるためと考えるのがよさそうです。

Timer を使用するには、まずインスタンスを作成します。コンストラクタの引数にはイベントまでの待ち時間をミリ秒単位で指定します。

var myTimer:Timer = new Timer(1000);

次に、指定時間後に呼び出されるイベントハンドラを登録してから start() メソッドを呼びます。イベント名は timer です。経過時間のカウントは start() が呼ばれるまで開始されません。

myTimer.addEventListener("timer", timerHandler);
myTimer.start();
 
private function timerHandler(event:TimerEvent):void {
  // ここに必要な処理を記述
  trace(event.toString());
}

インスタンス生成後でも delay 属性の値を代入することで待ち時間を変えることが可能です。

コンストラクタの第二引数には、イベント発生を繰り返す回数を指定することができます。デフォルト値は 0 です。0 は無限に繰り返すという意味になります。

// 1 秒間隔で 2 回イベントを発生する
var myTimer:Timer = new Timer(1000, 2);

繰り返しの回数も repeatCount 属性から設定可能です。現在までの繰り返し回数は currentCount 属性から参照できます(こっちは参照のみです)。 設定された回数分だけ繰り返したら timerComplete イベントがディスパッチされ、Timer は停止します。ちなみに Timer が実行中かどうかは running 属性で判断することができます。

var myTimer:Timer = new Timer(1000);
 
myTimer.repeatCount = 2;
myTimer.addEventListener("timerComplete", timerCompleteHandler);
myTimer.start();
 
private function timerCompleteHandler(event:TimerEvent):void {
  trace("timer complete handler called");
}

stop() メソッドと reset() メソッド

stop() メソッドを使うと Timer を一旦中断することができます。その後 start() を呼ぶと残りの回数分 Timer が実行されます。

var myTimer:Timer = new Timer(1000, 2);
myTimer.addEventListener("timer", timerHandler);
myTimer.start();
 
private function timerHandler(event:TimerEvent):void {
  myTimer.stop();
  trace(myTimer.running); // false が出力される
  myTimer.start();
}

reset() メソッドを使うと Timer の処理を停止し currentCount を 0 に戻します。 従って下記のサンプルは無限にイベントが繰り返されます。

var myTimer:Timer = new Timer(1000, 2);
myTimer.addEventListener("timer", timerHandler);
myTimer.start();
 
private function timerHandler(event:TimerEvent):void {
  myTimer.reset();
  trace(myTimer.currentCount); // 0 が出力される
  myTimer.start();
}

Posted by ackie at 08:15 PM | Comments (0)

March 24, 2006

String と正規表現

String クラス (livedocs@lab) のいくつかのメソッドは正規表現と一緒に使うことができます。文字列の中から特定の表現を抜き出したり置き換えたりしたいときにはなかなか便利です。

では、早速具体的なメソッドを見ていきましょう。

split() メソッド

split() メソッドは引数に渡されたパターンを区切りとして文字列を分解します。結果は配列に格納されます。

vvar myStr:String = "Happy like a honeybee";
trace(myStr.split("a"));
// H,ppy like , honeybee が出力される

引数には正規表現も使えます。空白文字を区切りに指定してみます。

var myStr:String = "Happy like a honeybee";
var myPattern:RegExp = /\s+/;
trace(myStr.split(myPattern));
// Happy,like,a,honeybee が出力される

正規表現に括弧が含まれると括弧内に一致した箇所も出力に含まれます。先ほどの正規表現に括弧を足してみます。

var myStr:String = "Happy like a honeybee";
var myPattern:RegExp = /(\s+)/;
trace(myStr.split(myPattern));
// Happy, ,like, ,a, ,honeybee が出力される

オプションとして返す要素の最大数を指定できます。これは2つ目の引数に指定します。

var myStr:String = "Happy like a honeybee";
var myPattern:RegExp = /(\s+)/;
trace(myStr.split(myPattern, 3));
// Happy, ,like が出力される

select() メソッド

select() メソッドは引数のパターンに一致する箇所があればその先頭文字のインデックスを返します。見つからなかった場合は -1 を返します。パターンには String 型または RegExp 型のオブジェクトを使用できます。

var myStr:String = "Happy like a honeybee";
trace(myStr.search("li")); // 6 が出力される
 
var myPattern:RegExp = /(\w)\1/;
trace(myStr.search(myPattern)); // 2 が出力される

select() メソッドでは最初に見つかった箇所しか知ることができません。g フラグや lastIndex は無視されます。

ちなみに indexOf() メソッドと lastIndexOf() メソッドを使えば検索開始位置を指定できます。ただし、正規表現は使用できません。

var myStr:String = "Happy like a honeybee";
// 6 文字目から後ろへ検索する
trace(myStr.indexOf("a", 5)); // 11 が出力される
// 6 文字目から前へ検索する
trace(myStr.lastIndexOf("p", 5));; // 3 が出力される

lastIndexOf() はインデックス値を指定すると、指定された箇所から前に検索を行います。上の例では4文字目の p と先に一致するため返り値は 3 になっています。

match() メソッド

match() メソッドは引数のパターンに一致した箇所を配列として返します。パターンには文字列または正規表現を使用できます。

var myStr:String = "Happy like a honeybee";
var myPattern:RegExp = /(\w)\1/;
trace(myStr.match(myPattern)); // pp が出力される

上記のように正規表現に g フラグが無い場合最初に一致した箇所のみが返されますが、g フラグを付けると文字列全体を検索します。

var myStr:String = "Happy like a honeybee";
var myPattern:RegExp = /(\w)\1/g;
trace(myStr.match(myPattern)); // pp,ee が出力される

いずれの場合も lastIndex の値は使用されません。

replace() メソッド

replace() メソッドは1つ目の引数で指定されたパターンに一致した箇所を2つ目の引数を使って置き換えます。置き換え済の文字列が戻り値になります。

var myStr:String = "Happy like a honeybee";
var myPattern:RegExp = /(\w)\1/g;
trace(myStr.replace(myPattern, "xx")); 
// Haxxy like a honeybxx が出力される

パターンには文字列と正規表現が使えます。オリジナルの文字列は変更されません。

でも、これだといつも置き換えに使う文字列は同じものになってしまいますね。そのため、第2引数には関数を指定することもできるようになっています。関数は一致するごとに呼ばれ、最初の引数に一致した部分、最後から2番目の引数に一致した箇所の先頭のインデックス、最後の引数に元の文字列が渡されます。

var myStr:String = "Happy like a honeybee";
trace(myStr.replace("li", myFunc));
// Happy li6ke a honeybee が出力される
 
private function myFunc(...args):String {
  trace(args);
  // li,6,Happy like a honeybee が出力される
  return args[0] + args[1];
}

パターンに正規表現を使用した場合、正規表現が括弧を含むとそれぞれの括弧に一致した箇所も2つ目以降の引数として括弧の数だけ渡されます。

var myStr:String = "Happy like a honeybee";
var myPattern:RegExp = /(\w)\1/g;
trace(myStr.replace(myPattern, myFunc));
// Hapy like a honeybe が出力される
 
private function myFunc(...args):String {
  trace(args);
  // ee,e,19,Happy like a honeybee が出力される
  return args[1];
}

Posted by ackie at 05:18 PM | Comments (0)

March 23, 2006

AS3 と正規表現

AS3 では ECMAScript3 に定義されている正規表現が使用できます。これで文字列の操作もずいぶん便利になりました。おかげで前より普通にスクリプト言語らしくなった気がします。

RegExp クラス

正規表現は「数字だけから成る文字列」とか「<p> と </p> で囲まれた文字列」のようなパターンを記述するための表現です。ECMAScript3 の仕様では、先に挙げた2つの例であればそれぞれ \d+ と <p>.*</p> といった記述ができます。

これらの正規表現を AS3 で使用するには RegExp クラス (livedocs@lab) を使用します。対象となる具体的なパターンを / (スラッシュ)で挟むと RegExp のインスタンスの初期化ができます。

var myPattern1:RegExp = /\d+/;
var myPattern2:RegExp = /<p>.*</p>/g;

2行目の例ではパターンの後に g が付いています。このようにフラグを指定することも可能です。ちなみに g フラグは global の略で複数のマッチングをする際の指定になります。正規表現を使い慣れた人にはお馴染みですね。

オブジェクト指向好きならコンストラクタを使っても同様にインスタンスが生成できます。

var myPattern1:RegExp = new RegExp("\d+");
var myPattern2:RegExp = new RegExp("<p>.*</p>", "g");
 
trace(myPattern1.source); // \d+ が出力される

ちなみにインスタンスの持つパターンは source 属性から参照できます。この値は参照のみが可能です。

test() メソッド

RegExp クラスの test() メソッドは引数として渡された文字列に、あるパターンが含まれているかをチェックします。

var myStr:String = "0123456789";
var myPattern:RegExp = /12/;
var result:Boolean = myPattern.test(myStr);
trace(result); // true が出力される
trace(myPattern.lastIndex); // 3 が出力される

上の例では、myStr の2文字目と3文字目に myPattern (1と2の連続)が一致するため結果は true になっています。

また、結果が true のとき、lastIndex には一致した箇所の最後の文字のインデックス値が設定されます。

test() を呼ぶとき lastIndex 属性を使うとパターンマッチングを開始する文字を指定することができます。例えば lastIndex を 2 にすると最初の2文字はスキップされます。ただし、このとき正規表現に g フラグを設定する必要があります。

var myStr:String = "0123456789";
var myPattern:RegExp = /12/g;
myPattern.lastIndex = 2; var result:Boolean = myPattern.test(myStr); trace(result); // false が出力される

上の例は3文字目からチェックを開始するために結果が false になります。

exec() メソッド

exec() メソッドは引数の文字列内でパターンに一致した箇所の文字列を含むオブジェクトを返します。一致する箇所が無い場合の返り値は null です。

var myStr:String = "Happy like a honeybee";
// 小文字の a から z のみが連続するパターン
var myPattern:RegExp = /[a-z]+/;
var result:Object = myPattern.exec(myStr);
trace(result[0]); // appy が出力される
trace(result.index); // 1 が出力される
trace(result.input); // Happy like a honeybee が出力される

結果のオブジェクトには一致した文字列の他に、一致した箇所の先頭を示す index 属性と渡された文字列を保持する input 属性があります。

パターンが括弧を含む場合、結果のオブジェクトはそれぞれの括弧に一致した箇所も返します。配列の2番目以降のアイテムが括弧の登場する順番に一致した値になります。次の例では同じ文字が2回以上続くパターンを括弧を一つ使い指定しています。このため結果には (1) 一致した箇所全体と (2) 括弧の中と一致した部分が含まれます。

var myStr:String = "Happy like a honeybee";
var myPattern:RegExp = /(\w)\1/;
var result:Object = myPattern.exec(myStr);
trace(result[0]); // pp が出力される
trace(result[1]); // p が出力される - (\w) に一致した文字列
trace(result.index); // 2 が出力される
trace(result.input); // Happy like a honeybee が出力される

exec() メソッドも test() と同様に g フラグが設定されていれば lastIndex でマッチングを開始する箇所を指定できます。

var myStr:String = "Happy like a honeybee";
var myPattern:RegExp = /(\w)\1/g;
myPattern.lastIndex = 5; var result:Object = myPattern.exec(myStr); trace(result[0]); // ee が出力される trace(result.index); // 19 が出力される trace(result.input); // Happy like a honeybee が出力される

一致した文字列が見つかると lastIndex 属性に一致した箇所の最後の文字のインデックス値が設定されます。これも test() と一緒です。この仕様とループを組み合わせれば、文字列の最初から一つ一つ一致する箇所を探すこともできます。

var myStr:String = "Happy like a honeybee";
var myPattern:RegExp = /(\w)\1/g;
var result:Object = myPattern.exec(myStr);
while (result != null) {
  trace (result.index + ":" + result[0]);
  result = pattern.exec(str);
}
// 以下の2行が出力される
// 2:pp
// 19:ee

上のサンプルで g フラグを忘れると無限ループになります。間違えやすいので注意しましょう。

Posted by ackie at 06:20 PM | Comments (0)

March 08, 2006

ApplicationDomain

Loader オブジェクトで (URLLoader ではありません) swf ファイルをロードするとき、ロード先のアプリケーションドメインを指定することができます。

アプリケーションドメインはクラス定義の単位になるため、実行時に swf をロードすることで動的にクラス定義を追加するといった使い方ができます。また、複数のアプリケーションドメインを持つことができるため、同じクラスの異なるバージョンを同時にアプリ内で使うことも可能です。

それでは、アプリケーションドメインの使い方について詳しく見ていきましょう。

アプリケーションドメインの階層

通常、アプリケーションドメインは階層構造をなしています。一番親のアプリケーションドメインはシステムドメインと呼ばれ、Flash Player 上に一つだけ存在します。その他のアプリケーションドメインはシステムドメインの子孫ということになります。

各アプリケーションドメインにはクラス定義が含まれますが、子のドメインは親のドメインのクラス定義を引き継ぎます。従って、下の階層になるほど多くのクラスが利用可能なわけです。

アプリケーションドメインは ApplicationDomain クラス (livedocs@lab) のインスタンスとして実装されています。ApplicationDomain のインスタンスを生成する際、親ドメインをコンストラクタの引数として指定することができます。何も指定されなければシステムドメインが親ドメインになります。

// システムドメイン下に新しいアプリケーションドメインを作成
new ApplicationDomain();
// 現在のドメイン下に新しいアプリケーションドメインを作成
new ApplicationDomain(ApplicationDomain.currentDomain);

ApplicationDomain.currentDomain は現在アプリケーションが実行されているドメインを示す静的変数です。

ロード時のアプリケーションドメインの指定

swf ファイルロード時に読込先のアプリケーションドメインを指定するには URLRequest オブジェクトの applicationDomain 属性を使用します。

var myReq:URLRequest = new URLRequest();
myReq.url = "myClasses.swf";
// 現在のドメインを設定する
myReq.applicationDomain = ApplicationDomain.currentDomain;
var myLoader:Loader = new Loader();
myLoader.load(myReq);

読み込み後は Loader の loadeeInfo 属性から myLoader.loadeeInfo.applicationDomain のようにして読み込まれた swf が属するアプリケーションドメインにアクセスすることができます。

読み込まれた側から自身の属するアプリケーションドメインを取得するには、読み込まれた swf 内のルート DisplayObject の loaderInfo から参照します。roo 以外の DisplayObject からであれば this.root.loaderInfo.applicationDomain で取得できます。

getClass() メソッドによる動的なクラス定義の利用

さて、getClass() メソッドを使うとアプリケーションドメインからクラス定義を取得することができます。つまり、アプリケーションドメインに新しく swf ファイルを読み込むことで、利用できるクラス定義を動的に追加できるわけです。

ただし既存のクラス定義を、新しくロードした swf 内の定義で上書きすることはできません。新規に見つかったクラス定義のみが追加されます。

var myReq:URLRequest = new URLRequest();
myReq.url = "myClasses.swf";
// 現在のドメインに読み込むよう指定
myReq.applicationDomain = ApplicationDomain.currentDomain;
var myLoader:Loader = new Loader();
myLoader.addEventListener(Event.COMPLETE, completeHandler);
myLoader.load(myReq);
 
private function completeHandler(event:Event):void {
  // まず読込先のドメインを取得
  var myDomain:ApplicationDomain = myLoader.loadeeInfo.applicationDomain;
  // ドメイン内のクラス定義を取得
  var myClassRef:Class = myDomain.getClass("myClass1");
  // 定義を取得したクラスのインスタンス生成
  var foo:Object = new myClassRef();
}

上記の例ではアプリケーションを実行中のアプリケーションドメインに swf をロードしています。この場合、一旦追加されたクラス定義はそのまま残り続けます。

ですが、子のドメインに swf をロードすれば swf をアンロードすることで追加されたクラス定義もガーベッジコレクションの対象とすることができます。(もちろんロードした swf への参照がないことが条件ですが)

つまり、下のように書き換えた場合にはクラス定義の追加と削除がコントロールできるようになるわけです。

...
myReq.url = "myClasses.swf";
// 新規に子ドメインを作るよう変更
myReq.applicationDomain = new ApplicationDomain(ApplicationDomain.currentDomain);
var myLoader:Loader = new Loader();
myLoader.addEventListener(Event.COMPLETE, completeHandler);
myLoader.load(myReq);
 
...

別ドメインへのアプリケーションのロード

アプリケーションを読み込むときにアプリケーションドメインを使い分けることで、クラス定義への依存状況に柔軟に対応することができるようになります。

例えば、システムドメイン直下に新しいアプリケーションドメインを作成した場合、現在のドメインと新規ドメインのクラス定義は独立です。すなわち、アプリケーション固有のクラス定義は共有されません。

これを利用すると、同じアプリケーションの異なるバージョンを、クラス定義の違いなどを気にすることなく同時に実行することが可能です。

var myReq:URLRequest = new URLRequest();
// 別バージョンのアプリケーションを指定
myReq.url = "v2app.swf";
// システムドメイン下に新しいアプリケーションドメインを作成
myReq.applicationDomain = new ApplicationDomain();
var myLoader:Loader = new Loader();
// 独立した新規ドメインにアプリケーションをロード
myLoader.load(myReq);

また、複数の子ドメインにアプリケーションを分割すると、子ドメイン固有のクラス定義はそれぞれ独立させながら、親ドメインを共通のクラス定義の場として管理することができます。

例として、複数のパネルを持つポータル系のアプリケーションで、個々のパネル内を独立したアプリケーションとして実装した場合を考えてみます。

// パネル内のアプリケーションを指定
myReq1.url = "panel1.swf";
// 新規に子ドメインを作成
myReq1.applicationDomain = new ApplicationDomain(ApplicationDomain.currentDomain);
myLoader1.load(myReq);
 
// 別パネル内のアプリケーションを指定
myReq2.url = "panel2.swf";
// もう一つ子ドメインを作成
myReq2.applicationDomain = new ApplicationDomain(ApplicationDomain.currentDomain);
myLoader2.load(myReq);

このような構造だと panel1.swf に固有のクラス定義は例えば panel2.swf のような他のパネル内のアプリケーションには影響しません。そのため、共通部分(親のアプリケーションドメイン内のクラス定義)は管理しながら、かなり自由に個別の機能を開発/管理することができそうです。

Posted by ackie at 05:48 PM | Comments (2)

March 07, 2006

navigateToURL 関数

今回は、flash.net パッケージの関数2つを紹介します。

navigateToURL() 関数

AS3 アプリケーションからもう一つブラウザのウインドウを開きたい、あるいは今 AS3 アプリケーションを実行中のウインドウを他の HTML コンテンツで置き換えたいというときには navigateToURL() 関数 (livedocs@lab) を使います。以下が関数の定義です。

public function navigateToURL(request:URLRequest, window:String = null):void

1つめの引数には URLRequest オブジェクトを指定します。このオブジェクトには URLLoader クラスの記事内の例と同様に、リクエスト先の URL やヘッダ情報それから送信するデータと送信方法等を指定します。

2つ目の引数にはリクエストの結果を受信するウインドウを指定します。こちらはデフォルト値が定義されているため省略が可能です。省略した場合には新しいウインドウが開かれます。つまり "_blank" を指定したのと同等の結果になります。

下のサンプルでは navigateToURL() の第二引数を "_self" にしていますので AS3 アプリケーションを実行しているウインドウ自身のコンテンツを置き換えます。

import flash.net.navigateToURL;
 
var myVars:URLVariables = new URLVariables();
myVars.currentTime = new Date().getTime();
myVars.city = "Tokyo";
 
var myReq:URLRequest = new URLRequest();
myReq.url = "http://www.sample.com/weather";
myReq.data = myVars;
 
// 現在のウインドウのコンテンツを置き換える
navigateToURL(myReq, "_self");

navigateToURL() 関数を使うにはクラスを使用するときと同じ様にインポートします。

sendToURL() 関数

AS3 アプリケーションからサーバーにデータを送りたい、けれど結果は無視したいというときには sendToURL() 関数 (livedocs@lab) を使用します。使い方は上で紹介した navigateToURL() 関数とほぼ同じです。違いは2つ目の引数がないことくらいです。

public function sendToURL(request:URLRequest):void

あまり芸がないのですが、一応サンプルも。

import flash.net.sendToURL;
 
var myVars:URLVariables = new URLVariables();
myVars.user = "foo";
myVars.data = "hello";
 
var myReq:URLRequest = new URLRequest();
myReq.url = "http://www.sample.com/post";
myReq.data = myVars;
myReq.method = URLRequestMethod.POST;
// サーバーにデータを送信
sendToURL(myReq);

Posted by ackie at 05:32 PM | Comments (0)

March 06, 2006

URLLoader クラス

外部からの XML データ等を読み込むときは URLLoader クラス (livedocs@lab) を使うことができます。

以前紹介した Loader クラスは swf やイメージを主に表示目的でロードするためのクラスでした。Loader クラスの記事を書いていた頃は URLLoader は Loader のサブクラスでしたが、今は独立したクラスになっています。

URLRequest クラスとデータ要求の送信

URLLoader を使ってサーバーにリクエストを送るには、まず URLRequest クラス (livedocs@lab) のインスタンスを作ります。URLRequest のオブジェクトにはリクエスト先の URL やヘッダ情報等が指定できます。

次に、作成した URLRequest オブジェクトを引数として URLLoader の load() メソッドを呼び出します。

var myReq:URLRequest = new URLRequest();
myReq.url = "foo.xml";  // コンストラクタの引数としても指定可能
var myLoader:URLLoader = new URLLoader();
myLoader.load(myReq);

HTTP ヘッダに情報を追加する場合は、項目ごとに URLRequestHeader クラス (livedocs@lab) のインスタンスを作成し、 URLRequestHeader オブジェクトの requestHeaders 属性(Array 型です)に追加します。

var myReq:URLRequest = new URLRequest("foo.xml");
var myRH:URLRequestHeader = new URLRequestHeader("pragma", "no-cache");
myReq.requestHeaders.push(myRH);
var myLoader:URLLoader = new URLLoader();
myLoader.load(myReq);

受信したデータの処理

load() を実行してからデータの読み込み処理が終わるまで ActionScript から読み込んだデータにアクセスすることはできません。URLLoader は受信処理を完了すると complete イベントを発生させるので、 complete イベントに対するイベントハンドラ内でデータ操作を行うようにします。読み込まれたデータは URLLoader の data 属性からアクセスすることができます。

myLoader.addEventListener(Event.COMPLETE, completeHandler);
myLoader.load(new URLRequest("foo.xml"));
 
private function completeHandler(event:Event):void {
  trace(myLoader.data);
}

参考までに、complete を含め URLLoader がディスパッチするイベントには以下のものがあります。

complete      : ダウンロード処理が完了した
httpStatus    : 返信の HTTP ステータスが判明した
ioError       : IO エラーによりロード処理が中断した
open          : ロード処理が開始した
progress      : ロードを実行中
securityError : 許可されない通信を行おうとした

さて、読み込まれただデータは DataFormat クラス (livedocs@lab) に定義されている3種類のフォーマットのどれかです。URLLoader オブジェクトの dataFormat 属性から値を知ることができます。

  • DataFormat.TEXT : String 型 (livedocs@lab) の文字列
  • DataFormat.BINARY : ByteArray 型 (livedocs@lab) のバイナリデータ
  • DataFormat.VARIABLES : URL エンコードされたフォーム変数を持つ URLVariables オブジェクト

3つ目の DataFormat.VARIABLES フォーマットの際に使用される URLVariables クラス (livedocs@lab) からは名前と値がペアになった連想配列として値を取り出すことができます。

データの送信

サーバにデータを送信する際も上記3種類のフォーマットが使用できます。送信するデータを URLRequest オブジェクトの data 属性にセットすれば、リクエスト時にセットしたデータが送られます。

下の例では変数&値のペアを定義した URLVariables オブジェクトを POST で送信しています。この場合、データは x-www-form-urlencoded フォーマットにエンコードされて送られます。

var myVars:URLVariables = new URLVariables();
myVars.userID = "guest";
myVars.password = "foo";
myReq.data = myVars;
myReq.url = "ww.sample.com/foo.jsp";
myReq.method = URLRequestMethod.POST;
myLoader.load(myReq);

送信方法 (GET/POST)は URLRequest オブジェクトの method 属性に URLRequestMethod クラス (livedocs@lab) に定義された値を使って指定します。デフォルトは GET です。POST する場合はデータの MIME タイプを URLRequest オブジェクトの contentType 属性に設定することができます。 ByteArray オブジェクトを送信する場合は GET は使用できません。

Posted by ackie at 05:42 PM | Comments (0)

March 02, 2006

QName クラスの続き

もう少しだけ QName の話題を続けます。

QName と child() メソッド

child() や attribute() を呼ぶときに修飾名を使う必要があれば QName を引数として渡すことができます。以下のサンプルを使って具体的に見てみましょう。

var myOrder:XML =
<foo:order xmlns:foo="http://www.sample.com/foo/">
  <foo:item bar:id="1" quantity="1" xmlns:bar="http://www.sample.com/bar/">
    <name>fresh burger</name>
    <price>300</price>
  </foo:item>
</foo:order>;

まず foo:item に相当する QName のオブジェクトを作成します。これを引数にすることで child() を使っても foo:item を指定できるようになります。

var fooNS:Namespace = myOrder.namespace("foo");
var fooItemQN:QName = new QName(fooNS, "item");
trace(myOrder.child(fooItemQN).name());
// http://www.sample.com/foo/::item が出力される

ちなみに child() の箇所は以下のように書き換えることもできます。

// 以下の2行は同じオブジェクトを指す
myOrder.child(fooItemQN);
myOrder.fooNS::["item"];

QName と attribute() メソッド

attribute() も child() と同様に QName を使って修飾名を指定することができます。

var barNS:Namespace = myOrder.child(0).namespace("bar");
var barIdQN:QName = new QName(barNS, "id");
trace(myOrder.child(fooItemQN).attribute(barIdQN));
// 1 が出力される

attribute() も代わりになる書き方が幾つかあります。どれがお好みですか?

// 以下の3行は同じ属性を指す
trace(myOrder.child(fooItemQN).attribute(barIdQN));
trace(myOrder.fooNS::["item"].@barNS::["id"]);
trace(myOrder.fooNS::["item"].barNS::["@id"]);

Posted by ackie at 05:13 PM | Comments (0)

March 01, 2006

QName クラス

QName クラス

QName クラス (livedocs@lab) は XML の要素や属性の修飾名を扱うために使われます。QName のインスタンスは uri と localName という属性を持っています。それぞれ uri 属性は名前空間修飾子 localName 属性はローカル名の値を持ちます。

var qn:QName = new QName("www.sample.com/uri/", "myElementName");
trace(qn.uri);
// www.sample.com/uri/ が出力される
trace(qn.localName);
// myElementName が出力される

name() メソッドと localName() メソッド

name() メソッドは XML オブジェクト(またはXMLList オブジェクト)の修飾名を、localName() メソッドはローカル名を取得するのに使用します。対象が XMLList オブジェクトの場合はリスト内に一つだけアイテムを持つオブジェクトである必要があります。

var myOrder:XML =
<foo:order xmlns:foo="http://www.sample.com/foo/">
  <foo:item id="1" quantity="1" xmlns="http://www.sample.com/bar/">
    <name>fresh burger</name>
    <price>300</price>
  </foo:item>
</foo:order>;
// 修飾名とローカル名を取得する
trace(myOrder.child(0).name());
// http://www.sample.com/foo/::item が出力される
trace(myOrder.child(0).localName());
// item が出力される

name() メソッドの返す型は QName のため localName 属性を使用してもローカル名を取得できます。また名前空間の URI は uri 属性から取得できます。一方、プレフィックス(この場合は foo)情報は QName は持っていません。プレフィックスが必要な場合は namespace() メソッドを使用します。

trace(myOrder.child(0).namespace().prefix);
// foo が出力される
trace(myOrder.child(0).namespace().uri);
// http://www.sample.com/foo/ が出力される
trace(myOrder.child(0).name().uri);
// http://www.sample.com/foo/ が出力される
trace(myOrder.child(0).name().localName);
// item が出力される
trace(myOrder.child(0).localName());
// item が出力される

Qname および Namespace の uri 属性はプレフィックスがなくてもデフォルトの名前空間に属するオブジェクトには値が設定されます。例えば foo:item ノードではデフォルトの名前空間が宣言されています。そのため name ノードの QName を調べると以下のようになります。

trace(myOrder.child(0).child(0).name());
// http://www.sample.com/bar/::name が出力される

関連する名前空間がない場合 uri 属性は空文字になります。

setName() メソッドとsetLocalName() メソッド

setLocalName() メソッドはローカル名のみを引数の文字列と置き換えます。

trace(myOrder.child(0).name());
// http://www.sample.com/foo/::order が出力される
myOrder.child(0).setLocalName("purchase");
trace(myOrder.child(0).name());
// http://www.sample.com/foo/::purchase が出力される

setName() メソッドは引数が文字列の場合修飾名を置き換えますが引数はローカル名として扱われます。

myOrder.child(0).setName("purchase");
trace(myOrder.child(0).name());
// purchase が出力される

setName() に引数としてQName オブジェクトを渡すと名前空間の宣言付きで置き換えることができます。

var myQN:QName = new QName("http://www.sample.com/baz/", "title");
myOrder.child(0).child(0).setName(myQN);
trace(myOrder.child(0).child(0).toXMLString());
// <aaa:title xmlns:aaa="http://www.sample.com/baz/">fresh burger</aaa:title> が出力の変更分

aaa は自動的に振られたプレフィックスです。

Posted by ackie at 05:39 PM | Comments (0)

February 27, 2006

XML名前空間の使用

E4X では XML の名前空間も使用できます。ここで登場するNamespace クラス (livedocs@lab) は以前にアクセス制御の使用目的で紹介した名前空間の Namespace と同じものです。

今回は、以下のサンプルを使って名前空間で修飾された要素や属性へのアクセス方法を説明します。

var myOrder:XML =
<foo:order xmlns:foo="http://www.sample.com/foo/">
  <foo:item bar:id="1" quantity="1" xmlns:bar="http://www.sample.com/bar/">
    <bar:name>fresh burger</bar:name>
    <price>300</price>
  </foo:item>
  <foo:item bar:id="2" quantity="1" xmlns:bar="http://www.sample.com/bar/">
    <bar:name>french fries</bar:name>
    <price>170</price>
  </foo:item>
</foo:order>;

前置修飾子で修飾された要素を扱うには、修飾子に対応する Namespace のインスタンスが必要です。例えば、上記の例では item に foo という前置修飾子付が付いていますが、これは item が何らかの名前空間に属していることを意味します。そのため修飾子情報なしで myOrder.item と記述しても存在しないノードを指すことになってしまいます。

そこで、まず以下のように foo に対応する名前空間のオブジェクト(ここでは fooNS)を取得します。取得には namespace() メソッドを使用します

var fooNS:Namespace = myOrder.namespace("foo");
trace(fooNS); // http://www.sample.com/foo/ が出力される

foo はルートノードで定義されているため myOrder から直接取得しています。名前空間のスコープ内のノードであればどのノードからも同様に名前空間のオブジェクトが取得できます

取得した名前空間オブジェクトは、後に :: を付けると要素名の修飾に使用できます。今回の例では fooNS::item と記述すると item 以下にアクセスすることができるようになります。

trace(myOrder.fooNS::item[0].@quantity);
// 1 が出力される

次に、bar という名前空間も使用されていますので、これも扱えるようにします。bar は foo:item 要素で定義されていますので次のように Namespace オブジェクトを取得してみます。

var barNS:Namespace = myOrder.fooNS::item[0].namespace("bar");
trace(barNS); // http://www.sample.com/bar/ が出力される

これはちょっと長いので大変、という人は以下の方法でもOKです。この場合は URI がわかっている必要があります。

var barNS:Namespace = new Namespace("bar", "http://www.sample.com/bar/");

これで全ての要素が扱えるようになりました。

trace(myOrder.fooNS::item[0].@barNS::id);
// 1 が出力される
trace(myOrder.fooNS::item[0].barNS::name); // fresh burger が出力される

Posted by ackie at 05:22 PM | Comments (0)

February 24, 2006

XML 属性の操作

属性値を取得するためのメソッドとしては attributes() と attribute() があります。

public attributes() : XMLList
public attribute(attributeName:String) : *

attributes() は対象となるノードに含まれる全ての属性値を返します。子孫のノードの属性は含みません(念のため)。戻り値の型は XMLList です。複数のオブジェクトを持つ XMLList に対して呼ばれた場合はリスト内の個々のオブジェクトの属性値をまとめて返します。

attribute() は取得する属性の名前を引数で指定します。その他の動作は基本的に attributes() と同様です。こちらのメソッドも戻り値の型は XMLList (のはず)です。

使い方の例を示します。

var myOrder:XML =
<order>
  <item id="1" quantity="1">
    <name>fresh burger</name>
    <price>300</price>
  </item>
  <item id="2" quantity="1">
    <name>french fries</name>
    <price>170</price>
  </item>
</order>;
 
trace(myOrder.item.attributes());
// 1121
trace(myOrder.item.attribute("id"));
// 12

toString() メソッドは(toXMLString() メソッドも)属性値しか返しません。なので上記の出力だけを見ると attributes() メソッドの方はどんな属性の値なのかという情報が分かりませんね。そんなときは name() メソッドが役に立ちます。XMLList 内のオブジェクト一つ一つに呼び出すことで名前を知ることができます。

trace(myOrder.item.attributes()[0].name());
// id
trace(myOrder.item.attributes()[0]);
// 1
trace(myOrder.item.attributes()[1].name());
// quantity
trace(myOrder.item.attributes()[1]);
// 1

@ と attribute()

@と変数を組み合わせても attribute() メソッドと同じように属性値を得ることができます。以下の3つの記述はそれぞれ同じ値になります。一番下のは前の記事で要素を取り出す方法として紹介したものと同じ形式です。

trace(myOrder.item.attribute("quantity"));
trace(myOrder.item.@["quantity"]);
trace(myOrder.item["@quantity"]);
// どの場合も 11 が出力される

Posted by ackie at 05:39 PM | Comments (0)

February 22, 2006

XML 要素の操作

今回は要素を扱うためのメソッドをいくつか紹介します。

child() メソッド

1つ目は、child() メソッドです。child() メソッドは . (ドット)オペレータと組み合わせて使用します。引数には要素名を指定できます。次の例では myOrder.item.child("name") という使い方がされていますが、これは myOrder.item.name と同じ結果になります。

var myOrder:XML =
<order>
  <item  id="1" quantity="1">
    <name>fresh burger</name>
    <price>300</price>
  </item>
  <item  id="2" quantity="1">
    <name>french fries</name>
    <price>170</price>
  </item>
</order>;
trace(myOrder.item.child("name"));
// 以下が出力される
// <name>fresh burger</name>
// <name>french fries</name>

XMLList に対して child() メソッドを呼ぶとリスト内のオブジェクトそれぞれに対して child() が呼ばれます。上の例では myOrder.item は2つのオブジェクトを持つ XMLList です。そのため2つの name ノードが出力されるわけです。(ちなみに myOrder.item.child("name") と myOrder.item["name"] も同じ結果になります)

さて、child() メソッドの引数にはインデックス値を指定することもできます。この場合は子ノードの順番を指定することになります。例えば下の例のように 0 を引数にすると item ノードの最初の子ノードが対象になります。

trace(myOrder.item.child(0));
// 以下が出力される
// <name>fresh burger</name>
// <name>french fries</name>

そのほかに、引数に "*" (アスタリスク)を渡すと child() は全ての子ノードを返します。これは children() メソッドを呼んだ場合と同じ結果になるようです。

trace(myOrder.item.child("*"));
// 以下が出力される
// <name>fresh burger</name>
// <price>300</price>
// <name>french fries</name>
// <price>170</price>

parent() メソッド

次は parent() です。このメソッドを使用すると親ノードを取得することができます。parent() は複数の親ノードが存在するような XMLList には使用することができません。

例えば、myOrder.item には2つのオブジェクトが含まれますがどちらの親も myOrder のため myOrder.item.parent() は myOrder になります。一方、myOrder.item.name.parent() は2つの name ノードの親がそれぞれ違う item ノードのため結果は undefined になります。

trace(myOrder.item.parent());
// <order>
// ... 中略
// </order>
trace(myOrder.item.name.parent());
// undefind が出力される

子ノードの追加

子ノードの追加には目的に応じていくつかのメソッドがあります。

最初の2つはリストの最初か最後にノードを追加します。引数は XML オブジェクトまたは XML オブジェクトの初期値となる文字列です。

public appendChild(child:Object) : XML
public prependChild(child:Object) : XML

具体的な例を示します。

var x1:XML =
<family>
  <kid>child1</kid>
  <kid>child2</kid>
</family>
x1.appendChild(<kid>child3</kid>);
x1.prependChild(<kid>child0</kid>);
trace(x1);
// <family>
//  <kid>child0</kid>
//  <kid>child1</kid>
//  <kid>child2</kid>
//  <kid>child3</kid>
//</family>

残りの2つは第1引数で指定された XML オブジェクトの前もしくは後に第2引数の XML オブジェクトを挿入します。

public insertChildBefore(child1:Object, child2:Object):XML
public insertChildAfter(child1:Object, child2:Object):XML

こちらもサンプルを。

var x2:XML =
<family>
  <kid>child1</kid>
  <kid>child2</kid>
</family>
x2.insertChildBefore(x2.kid[0], <kid>child0</kid>);
x2.insertChildAfter(x2.kid[2], <kid>child3</kid>);
trace(x2);
// <family>
//  <kid>child0</kid>
//  <kid>child1</kid>
//  <kid>child2</kid>
//  <kid>child3</kid>
//</family>

Posted by ackie at 05:53 PM | Comments (0)

February 21, 2006

XML から文字列への変換

XML オブジェクトを文字列に変換するには toString() メソッドか toXMLString() メソッドを使います。どちらもほぼ同じ出力になりますが、テキストノードを出力しようとしたときだけ違う動きをします。toXMLString() ではタグ付きで出力されますが toString() の場合はコンテンツのみが出力されます。

var myOrder:XML =
<order>
  <item  id="1">
    <name>fresh burger</name>
  </item>
</order>;
 
trace(myOrder.item[0].name.toXMLString());
// <name>fresh burger</name> が出力される
trace(myOrder.item[0].name.toString());
// fresh burger が出力される

文字列変換の際、prettyIndent 属性の値を変えると字下げする文字数を変えることができます。デフォルトは2です。また、 prettyPrinting 属性によりタグ間のスペースの処理の有無を指定できます。

これらの属性の指定には setSettings() メソッドが使用できます。setSettings() は static メソッドですのでインスタンスではなくクラスオブジェクトに対して呼び出します。クラスオブジェクトの属性を直接変更することもできます。

// どちらも効果は同じ
XML.setSettings({prettyIndent:0});
XML.prettyIndent = 0;

defaultSettings() メソッドを使用すると setSettings() メソッドで変更した属性をデフォルト値に戻すことができます。setSettings() を引数なしで呼び出してもデフォルト値が設定されます。

コメントと PI

デフォルトではコメントと PI は無視されますが、ignoreComments と ignoreProcessingInstructions の値を変更するとそれぞれパースの対象にできます。これらの属性はパース時に使用されるので、インスタンスの初期化後に値を変更しても効果はありません。

XML.ignoreComments = false;
XML.ignoreProcessingInstructions = false;
 
var myOrder:XML =
<order>
  <!-- comment -->
  <?PI processingInstructions ?>
  <item id="1" quantity="1"/>
</order>;
trace(myOrder);
// <order>
//   <!-- comment -->
//   <?PI processingInstructions ?>
//   <item id="1" quantity="1"/>
// </order>

パースされたコメントと PI は comments() と processingInstructions() メソッドで取得できます。返り値の型は XMLList です。

trace(myOrder.comments()[0]);
// <!-- comment -->
trace(myOrder.processingInstructions()[0]);
// <?PI processingInstructions ?>

Posted by ackie at 05:25 PM | Comments (0)

February 15, 2006

XMLList のイテレーション

前の記事で書いたように . や .. オペレータの結果は XMLList オブジェクトになります。この XMLList 内の XML オブジェクトを for 文を使って処理する方法を紹介します。for ... in と for each ... in の2種類のやり方があります。

まず、以下のような XML オブジェクト myOrder を考えます。myOrder.item は2つの item ノードを含む XMLList オブジェクトになります。

var myOrder:XML =
<order>
  <item  id="1" quantity="1">
    <name>fresh burger</name>
    <price>300</price>
  </item>
  <item  id="2" quantity="1">
    <name>french fries</name>
    <price>170</price>
  </item>
</order&item>;

for ... in のループを使うと、プロパティ名を使用してイテレーションを行うことができます。プロパティ名といっても XMLList 内の名前で要素名等とは関係ありません。for 文内ではプロパティ名を利用して XMLList から目的の XML オブジェクトにアクセスします。

var total:Number = 0;
for (var pname:String in myOrder.item) {
  total += myOrder.item[pname].@quantity 
         * myOrder.item[pname].price;
}

for each ... in 文では XMLList 内の XML オブジェクトを直接使用できます。上の例と同じ処理を for each 文を使って書き直してみます。

var total:Number = 0;
for each (var prop:XML in myOrder.item) {
  total += prop.@quantity 
         * prop.price;
}

こちらの方がすっきりした感じですね。これに with 文を使用してさらに単純化することもできます。

var total:Number = 0;
for each (var prop:XML in myOrder.item) {
  with (prop) {
    total += @quantity 
           * price;
  }
}

Posted by ackie at 08:41 PM | Comments (0)

February 14, 2006

XML 要素の操作

. と ..

AS3 では XML データの階層構造をたどって目的の要素にアクセスするのに . オペレータと .. オペレータを使用することができます。

どちらを使用した場合も、返される値の型は XMLList (livedocs@lab) です。 XML クラスはルートノードを一つしか持ちませんが XMLList クラスは複数持つことができます。メソッドは両者共通です。

では、下のサンプルを使って具体的な使い方をみてみましょう。

var eList:XML =
<employeeList>
  <employee id="1234">
    <lastName>taro</lastName>
    <firstName>yamada</firstName>
  </employee>
  <employee  id="2345">
    <lastName>hanako</lastName>
    <firstName>yamada</firstName>
    <title>as3 programmer</title>
  </employee>
</employeeList>

上記のコードで宣言された XML オブジェクト eList に対し、eList.employee は最上位のノード (employeeList) の子ノードで名前が employee のものを指します。上記の例ではそのようなノードが2つ存在するため eList.employee の値は2つの要素を持つ XMLList オブジェクトになります。

. (ドット) を繰り返すことで階層構造のさらに下を指定することができます。例えば eList.employee.lastName は、lastName という名前の要素が2つの employee それぞれに存在するため、やはり二つの要素を持つ XMLList オブジェクトになります。

.. アクセサを使用すると、階層の深さに関係なく関連する要素を取り出すことができます。例えば eList..lastName は eList オブジェクト内の lastName という名前の要素全てです。今回の例では eList.employee.lastName と同じ値になりますね。

さて、複数のノードが存在する場合、インデックス値を使って特定のノードを指定することができます。

eList.employee[0];  // 最初の employee ノード
eList.employee[1];  // 2つ目の employee ノード

今回の場合、下の2つはどちらも hanako を指します。

eList.employee[1].lastName
eList.employee.lastName[1]

なお、該当する要素が一つの場合インデックスは指定してもしなくても構いません。例えば eList.employee.title と eList.employee.title[0] は同じ要素を指します。

フィルターを用いた要素の指定

要素名の代わりに括弧内に条件を記述することでも要素を指定できます。以下に使い方の例を示します。要素名や属性の値を使って条件を記述します。

eList.employee.(lastName == "taro")     // 最初の employee ノード
eList.employee.(lastName == "taro").firstName // yamada
eList.employee.(lastName == "taro").@id // 1234
eList.employee.(@id == 2345)            // id が 2345 のノード
eList.employee.(@id == 2345).lastName   // hanako
eList.employee.(@id > 1000)             // 両方の employee ノード

要素の追加

. オペレータを使って要素を追加することができます。まず、次のような XML オブジェクトがあるとします。

var order1:XML =
<order> 
  <book> 
    <title>learning AS3</title> 
  </book> 
</order>

. オペレータで指定された新しい要素や属性に値を代入すると、XML オブジェクトが更新されます。

order1.book.@ISBN = 0123456789;
order1.book.price = 1000;
 
trace(order1);  // 以下の内容が出力される
<order> 
  <book ISBN="0123456789"> 
    <title>learning AS3</title> 
    <price>1000</price> 
  </book> 
</order> 

Posted by ackie at 06:33 PM | Comments (0)

February 13, 2006

E4X

E4X (ECMAScript for XML) は ECMAScript3 の拡張として開発された XML データを扱うための仕様です。2nd Edition が昨年末に公開されています(こちら)。AS3 では新しく E4X のクラスがサポートされ、従来よりもずっと簡単に XML データを扱うことができるようになりました。

AS3 でも AS2 で使われていた XML クラスは XMLDocument と名前を変えて他の関連クラス (XMLNode 等) と共に flash.xml パッケージに含まれています。しかし、これは過去に開発したコードのサポートが主目的で、今後の使用を推奨するものではありません。

さて、まず手始めに E4X の簡単な例を見てみましょう。以下のような XML 文書があるとします。

<order> 
  <book id="1"> 
    <title>learning AS3</title> 
  </book> 
</order> 

E4X では . や @ を使って要素にアクセスできます。上の XML 文が myOrder という XML オブジェクトに保持されているとすると、以下のような記述が可能です。

trace(myOrder.book.title);  // learning AS3 が出力される
trace(myOrder.book.@id);    // 1 が出力される

また、同様にデータの更新を行うこともできます。

myOrder.book.title = "learning Flex";
myOrder.book.@id = 2;
trace(myOrder);  
// 以下の内容が出力される
<order> 
  <book id="2"> 
    <title>learning Flex</title> 
  </book> 
</order> 

その他、Namespace など名前空間をサポートするためのクラスもあります。XML クラスの仕様はこちらをご覧ください(livedocs@lab)。

XML オブジェクトの初期化

E4X では XML 文をそのままオブジェクトにアサインして初期化することができます。配列の初期化に [] を使用したりオブジェクトの初期化に {} を使用するのと同様に XML の初期化に <> が使えます。

myXML:XML =
<order> 
  <book ISBN="0123456789"> 
    <title>learning AS3</title> 
  </book> 
</order> 

このとき、初期化データである XML 文の一部を変数にすることもできます。XML 文中に中括弧で囲んで変数を挿入すると、オブジェクトの初期化時に対応する文字列と置き換えられます。

var attrName:String = "ISBN";
var attrVal:String = "0123456789";
var tagName:String = "title";
var content:String = "learning AS3";
 
myXML:XML =
<order> 
  <book {attrName}={attrVal}> 
    <{tagName}>{content}</{tagName}>
  </book> 
</order> 
 
trace(myXML);
// 以下の内容が出力される
<order> 
  <book ISBN="0123456789"> 
    <title>learning AS3</title> 
  </book> 
</order>

もちろんコンストラクタを使用することもできます。その場合は、引数に文字列を渡します。

var str:String = "<order>"
               + "<book ISBN="0123456789">"
               + "<title>learning AS3</title>"
               + "</book>"
               + "</order>";
var myXML:XML = new XML(str);

Posted by ackie at 06:10 PM | Comments (0)

February 06, 2006

prototype 修飾子

Flex2 のベータ1からインスタンス変数の宣言に prototype 修飾子を使用することができるようになりました。例えば、以下の例では pProp という変数に prototype 修飾子が定義されています。

public class ProtoSample {
  public prototype var pProp:int = 10;
}

prototype 属性の変数は通常のインスタンス変数と同様に使用することができます。

var p1:ProtoSample = new ProtoSample();
trace(p1.pProp);  // 10 が出力される
p1.pProp = 20;
trace(p1.pProp);  // 20 が出力される

一方、クラスオブジェクトの持つ prototype オブジェクトから prototype 属性の変数の値を変更すると、そのクラスのインスタンスが複数存在する場合にはそれらにまとめて変更が反映されます。

var p1:ProtoSample = new ProtoSample();
var p2:ProtoSample = new ProtoSample();
trace(p1.pProp);  // 10 が出力される
trace(p2.pProp);  // 10 が出力される
// クラスの prototype オブジェクトから値を変更
ProtoSample.prototype.pProp = 20;
trace(p1.pProp);  // 20 が出力される
trace(p2.pProp);  // 20 が出力される

ただし、一旦直接値を変更したインスタンスに対しては変更が反映されません。

var p1:ProtoSample = new ProtoSample();
var p2:ProtoSample = new ProtoSample();
// p2 の pProp の値を直接変更
p2.pProp = 30;
// prototype オブジェクトから値を変更
ProtoSample.prototype.pProp = 20;
trace(p1.pProp);  // 20 が出力される
trace(p2.pProp);  // 30 が出力される

prototype 修飾子と関数

prototype 修飾子を使用できるのはインスタンス変数のみです。従ってメソッドの宣言に対して prototype 修飾子を使用することはできませんが、(代わりに?) Function 型のインスタンス変数には使うことができます。下の例では、doIt という Function 型のプロパティに prototype 属性を定義しています。

public class ProtoSample {
  public prototype var doIt:Function = 
    function() { trace("foo"); }
}

このように関数を prototype 属性付きで宣言しておくと、インスタンスの振る舞いをまとめて変えることができるようになります。

var p1:ProtoSample = new ProtoSample();
var p2:ProtoSample = new ProtoSample();
p1.doIt();  // foo が出力される
p2.doIt();  // foo が出力される
// prototype オブジェクト経由で別の関数を設定
ProtoSample.prototype.doIt = bar;
p1.doIt();  // bar が出力される
p2.doIt();  // bar が出力される
 
// 代入される関数の定義
function bar() {
  trace("bar");
}

prototype オブジェクトの簡単な補足

prototype オブジェクトを使用すると動的にプロパティを追加定義することができます(注:既存定義の上書き変更はできません)。追加されたプロパティは、prototype 修飾子付きで定義されたプロパティと同じように扱えます。

var p1:ProtoSample = new ProtoSample();
var p2:ProtoSample = new ProtoSample();
ProtoSample.prototype.pProp2 = 100;
ProtoSample.prototype.doIt2 = bar;
trace(p1.pProp2);  // 100 が出力される
trace(p2.pProp2);  // 100 が出力される
p1.doIt2();        // bar が出力される
p2.doIt2();        // bar が出力される
 
function bar() {
  trace("bar");
}

上のコードをコンパイルするには、コンパイルオプションに strict を使用しない、またはプロパティを追加するクラスを dynamic クラスとして宣言する必要があります。いずれにせよコンパイル時の型チェックがあまくなるので、可能な場合はクラス定義時に prototype 修飾子付きのプロパティとして定義しておいたほうが分かりやすいと思います。

Posted by ackie at 06:33 PM | Comments (0)

February 02, 2006

Flex2 Beta1 での変更点

Cairngorm 2.0 アルファも公開されました。(こちらです

さて、Flex2 のベータ1では Player API や言語レベルでの変更が行われています。このため、アルファ版で作成したコードは一部修正の必要があるケースがあります。今回は、変更された箇所の中から今までの記事に関連する項目をお伝えします。

* (no type) の導入

新しく、どの型でもないことを示す * が使用できるようになりました。変数や関数の戻り値がどんな値でも持てることを指定する際に使います。* は型ではありませんが、他の型と同じように使えます。

private var foo:*;
public function bar(arg:*):* {
  ...
}

ところで、上記のサンプルは型指定を何もしない場合と同じ意味になります。アルファ版では型指定をしないと Object 型の指定と同様に扱われていました。

private var foo;
public function bar(arg) {
  ...
}

また、a[i] のような配列の要素も Object ではなく * として扱われます。

Object 型の変更

* の導入により Object 型はいくつかの変更を受けています。まず、未定義時の値が null になります。undefined の値を持つことができるのは * として宣言された変数のみです。

private var foo:Object;
trace(foo);  // null が出力される

また、Object型 は自動的に型変換されなくなり、他の型に代入する際は明示的に as を使うなどしてキャストをする必要があります。 一方、* 宣言された変数は自動的に他の型に変換されます。

private var foo:Object = new Object();
private var bar:String = foo;  // エラー
 
private var baz:* = new Object();
bar = baz;  // OK

その他の型に関連する変更

void はもともとクラスではないとのことから、Void が void (全部小文字)に変更されました。とはいえ、従来どおり戻り値のない関数の型指定として使用します。

public function foo(o:Object):void {
  ...
}

as は元の値そのままか null のどちらかを返します。値の変換は行わなくなりました。

アクセス修飾子

クラスを private 宣言することができなくなりました。また、ヘルパークラスが必要な場合は、パッケージブロック外に記述します。

package sample {
  import flash.events.*;
  public class SampleClass {
    ...
  }
}
 
class PrivateClass {
  ...
}

イベント関連

従来、イベントの種類は専用のクラスに定義されていましたが、イベントクラスに定義が移っています。例えば、MouseEventType.MOUSE_UP は MouseEvent.MOUSE_UP になります。

EventDispatcher クラスの addEventListener() メソッドの priority プロパティの指定が逆になりました。数値の大きいほうが先に実行される(優先度が高い)ようになります。

Posted by ackie at 06:38 PM | Comments (0)

January 24, 2006

is 演算子

is は AS3 から新しく追加された演算子で、ある変数が特定の型に属しているか判定するときに使用します。is の左側の変数が右側の型のメンバーであれば式の値は true に、そうでなければ false になります。

var mySprite:Sprite = new Sprite();
trace (mySprite is Sprite);           // true が出力される

AS2 では instanceof 演算子が同じ目的で使用されていましたが、AS3 ではサポートされていません。代わりに is を使用するようにしましょう。instanceof 演算子を使用すると正しく判定されないケースが出てきます。例えば、下の例では Sprite のインスタンスに対して、3種類の型に対して判定を行っています。

var mySprite:Sprite = new Sprite();
trace (mySprite is Sprite);           // true が出力される
trace (mySprite is DisplayObject);    // true が出力される
trace (mySprite is IEventDispatcher); // true が出力される

Sprite は DisplayObject のサブクラスで、かつ IEventDispatcher を継承した EventDispatcher クラスのサブクラスです。従って全てのケースで true になるはずです。ところが、 is の代わりに instanceof 演算子を使用すると、下の2つは false になってしまいます。

var mySprite:Sprite = new Sprite();
trace (mySprite instanceof Sprite);           // true が出力される
trace (mySprite instanceof DisplayObject);    // false が出力される
trace (mySprite instanceof IEventDispatcher); // false が出力され

基本データ型と is

数値型や文字列の値の型判定は、型情報がないので実際の値を評価する必要があります。データ型について詳しくはまだ取り上げていませんが、とりあえず、以下に代表的と思われる例に対する値を表にしましたので参考にしてください。

値 

String

Number

int

uint

Boolean

Object

"foo"

true

false

false

false

false

true

"1"

true

false

false

false

false

true

null

false

false

false

false

false

false

undefined

false

false

false

false

false

false

true

false

false

false

false

true

true

false

false

false

false

false

true

true

0

false

true

true

true

false

true

1

false

true

true

true

false

true

-1

false

true

true

false

false

true

1.0

false

true

false

false

false

true

NaN

false

true

false

false

false

true

Posted by ackie at 03:25 PM | Comments (0)

January 13, 2006

Namespace とステートパターン

今回は名前空間を使ってステートパターンにトライしてみたいと思います。基本となるのは、名前空間とステートを一対一に対応させようという考え方です。

以前 Sprite とマウスイベントという記事で Sprite でボタンが作れることに簡単に触れていますが、今回はそれをステートパターンで実装してみます。

では、ボタンとなるクラスの定義を始めましょう。とりあえず、ボタンに必要な状態の定義から始めます。

StatePatternButton.as:
public class StatePatternButton extends Sprite{
  // ボタンの状態を名前空間として3種類定義する
  private static namespace out;   // マウスがボタンの外にある
  private static namespace over;  // マウスがボタンの上にある
  private static namespace down;  // マウスがボタンの上で押された
 
  // それぞれの状態に応じた振舞いを定義
  out function handle(e:Event):Void {
    // out の状態での振舞いを記述
  }
 
  over function handle(e:Event):Void {
    // over の状態での振舞いを記述
  }
 
  down function handle(e:Event):Void {
    // down の状態での振舞いを記述
  }
}

今回は各ステートにおける振る舞いを一つのメソッドとして実装できると仮定しています。この部分はステート毎の差異しだいで変数の宣言になったり共通のインターフェースを継承したクラスになったりすることと思います。

次にステート変更用の手段を定義します。

  // 現在のステートを保持する変数
  private var currentState:Namespace;
 
  // 新しいステートを設定する関数
  private function changeState(newState:Namespace):Void {
    currentState= newState;
  }

現在のステートを currentState という変数に保持することにします。ステートの変更には changeState 関数を使用します。

さらに、マウスイベントの種類と名前空間(ステート)の関連を定義します。

  // 連想配列の作成
  private var stateMap:Object = new Object();
 
  // イベントの種類とステートの関連付け
  private function initStateMap():Void {
  	stateMap[MouseEventType.MOUSE_OUT] = out;
  	stateMap[MouseEventType.MOUSE_OVER] = over;
  	stateMap[MouseEventType.MOUSE_UP] = over;
  	stateMap[MouseEventType.MOUSE_DOWN] = down;
  }
 
  // イベントタイプに応じたステートを返す
  private function getState(e:Event):Namespace {
    // ほんとは引数や戻り値が null だった場合の考慮も必要
    return stateMap[e.type] as Namespace;
  }

というわけでマウスイベントハンドラの定義です。呼び出される処理はイベントの種類ではなくステートに応じて呼び出されるため、イベントハンドラは一つだけです。イベントハンドラからは handleEvent という関数を呼び出します。この関数の内部でステートに応じた処理を呼び出します。

  // イベントハンドラ(種類に関係なく使用される)
  private function onMouseEvent(e:Event):Void {
    var ns:Namespace = getState(e);
    changeState(ns);
    handleEvent(e);
  }
 
  // 現ステート用の処理を呼び出す関数
  private function handleEvent(e:Event):Void {
    currentState::handle(e);
  }

あとは、コンストラクタ内で必要な初期化を行えばおしまいです。

  // 初期状態のステートを定義
  private static var initState:Namespace = out;
 
  public function StatePatternButton() {
    // ボタンとして動作するように設定
    buttonMode = true;
 
    // イベント種類とステートの対応表作成
    initStateMap();
 
    // 関連するマウスイベントにリスナ関数を登録
    addEventListener(MouseEventType.MOUSE_OUT, onMouseEvent);
    addEventListener(MouseEventType.MOUSE_OVER, onMouseEvent);
    addEventListener(MouseEventType.MOUSE_UP, onMouseEvent);
    addEventListener(MouseEventType.MOUSE_DOWN, onMouseEvent);
 
    // ステートの初期化
    changeState(initState);
    // 他に必要な初期化があれば
    // initSomethingElse();
  }
}

イベントの種類とハンドラ名を関連付ける代わりに、イベントタイプと名前空間(=ステート)を関連付けてみました。MOUSE_UP と MOUSE_OVER に同じ関数名を関連付けるよりはちょっと気持ちいい感じです。

以下のようなクラスを作って実際に動かしてみましょう。

package {
  import flash.display.Sprite;
 
  public class StatePattenTest extends Sprite {
    public function StateTest() {
      addChild(new StateButton());
    }
  }
}

というわけで名前空間の使用例を紹介してみました。名前空間はなじみがないとなんの役に立つのか想像しにくいものです。とりあえずでも名前空間の使い方について考えるきっかけになればと思います。

Posted by ackie at 05:08 PM | Comments (0)

January 12, 2006

Namespace でのアクセス制御

前回の記事で public 宣言された関数を名前空間を使って上書きできる云々を書きましたが、このことから推測できるように public も名前空間です。実際に public の別名を作ることもできます。

public static namespace myNS1 = public;

public に限らずアクセス修飾子は全て名前空間として扱われます。この辺りから、なぜ EcmaScript4 のプロポーザルでは protected の実装が必要ないと判断されたかなんとなく想像できそうですね。

オープンな名前空間には、明示的に宣言していなくても public, internal, protected, private は予め含まれています。そのために public や private 宣言された属性の参照に public:: 等をつける必要が無かったわけです。

インスタンス属性の修飾名による参照

ここで、ちょっと寄り道をして、インスタンスの属性を参照するときの名前空間の指定方法について説明します。

まず、以下のような名前空間&クラス定義があるとします。

myNS1.as:
public namespace myNS1
 
myNS2.as:
public namespace myNS2
 
SampleClass.as:
public class SampleClass {
  myNS1 var foo:int = 0;
  myNS2 var foo:int = 1;
}

この場合 SampleClass のインスタンスから foo を参照する際は名前空間を意識しなくてはなりません。属性参照時に明示的に名前空間を指定するには以下のように記述します。

var sc:SampleClass = new SampleClass();
sc.myNS1::foo = 100;

また、名前空間の指定に変数を使用することもできます。変数の型は Namespace になります。

var sc:SampleClass = new SampleClass();
var ns:Namespace = myNS1;
sc.ns::foo = 100;

名前空間によるアクセス制御の例

さて、準備もできたので、ここで名前空間を使ったアクセス制御の例を紹介します。C++ のフレンド関数もどきの機能を実現してみたいと思います。

public class WithSecret {
  private static namespace mySecretNS;
  // アクセスを許可するクラス名を設定
  private var myFriend:Object = MyFriend;
 
  public function checkFriend(target:Object):Namespace {
    if(target is myFriend) {
      return mySecretNS;
    } else {
      return null;
    }
  }
  // プライベートな名前空間に関数を定義
  myNS function secretFunction():Void { 
    trace("this is my secret");
  } 
}

クラス WithSecret には mySecretNS という名前空間に定義した関数 secretFunction があります。mySecretNS はプライベートな名前空間なので、他のクラスから直接指定することはできません。つまり secretFunction は事実上「見えない」関数になっています。

一方、特定のクラス(この例では MyFriend クラス)のインスタンスに対してのみ mySecretNS を返す関数も実装されています。受け取ったインスタンスは mySecretNS を指定できるため、以下のようにして秘密の関数を呼び出すことができます。

public class MyFriend {
  public function getSecret(obj:WithSecret) {
    var ns:Namespace = obj.checkFriend(this);
    if(ns != null) {
      obj.ns::secretFunction();
    }
  }
}

Posted by ackie at 06:55 PM | Comments (0)

January 11, 2006

オープンな名前空間

open namespaces (とりあえず本記事内では「オープンな名前空間」と訳すことにします)という名前空間のリストがあります。これは、修飾子の付かない属性の参照が行われたとき(例えば ns1::foo ではなく foo)、属性を検索するのに使用されるリストです。リスト内のどの名前空間にも該当する属性名が含まれていなければその属性は参照不可と判断されます。

ある名前空間をオープンな名前空間に宣言するには use namespace を使います。

public static namespace myNS1;
public static namespace myNS2;
 
myNS1 var foo:int = 0;
myNS2 var foo:String = "zero";
 
use namespace myNS1  // NS1 をオープンな名前空間に宣言
trace(foo)           // 0 が出力される

この例では2つの名前空間に foo という属性が定義されていますが、myNS1 がオープンな名前空間に宣言されてから foo が参照されたため myNS1::foo の方が参照されることになります。

オープンな名前空間に候補となる属性が2つ以上見つかった場合はエラーになります。例えば、上記の例で myNS2 も宣言してから foo を参照すると2つの候補が見つかるため参照の解決が不能な状態になってしまいます。このような場合は myNS2::foo のように修飾名を使用する必要があります。

オープンな名前空間は、クラス定義や関数定義などのブロック単位で有効です。

オープンな名前空間の使用例

オープンな名前空間に明示的に宣言された名前空間は public より優先されます。つまり、同名の属性が2つの名前空間にあっても、片方が public だと public 宣言された属性は無視され曖昧な参照にはなりません。これを利用して、例えばデフォルトの動作が public 関数で実装されている場合のバージョンアップを、元の関数を書き換える代わりに新しく関数を追加する形で行うことができます。

以下は具体的なサンプルです。(ところで、アルファ版ではバグのために以下のコードは動きません。public が同列に扱われてしまうためです。アルファ版で動かすには public の代わりに version1 などの名前空間を使うことになります。)

まず、以下のクラスが最初のバージョンとして作成されていたとします。

SampleClass.as:
public class SampleClass {
  // デフォルトの関数を定義
  public function myFunc() {}
}

次に、バージョンアップした関数を version2 の名前空間に同じ識別子で定義します。

version2.as:
// 新バージョン名を名前空間として定義
public namespace version2
 
SampleClass.as:
public class SampleClass {
  // デフォルトの関数を定義
  public function sampleFunc() {}
  // 新しい振舞いの関数を追加定義
  version2 function sampleFunc() {}
}

SampleClass を上記のように変更しても従来から SampleClass を使用していたプログラムには影響しません。一方、version2 の名前空間をオープンな名前空間に宣言すれば新しく追加された関数を使用することができます。

MyClass.as:
public class MyClass {
  // version2 をオープンな名前空間に宣言
  use namespace version2
 
  public function myFunc() {
    var sc:SampleClass = new SampleClass();
    // version2::sampleFunc が呼ばれる
    sc.sampleFunc();
  }
}

名前空間を使ったバージョン管理の例でした。

Posted by ackie at 04:23 PM | Comments (0)

January 10, 2006

Namespace

クラスに名前をつけることは、クラス内で定義されているプロパティをクラスの名前空間に結びつける行為と考えることができます。パッケージやインターフェースについても同様です。これらの名前空間はプログラムの構造と固く結びついていますが、namespace を使うとプログラムの構造から独立して名前空間を扱えるようになります。例えば、任意の単位でクラスの振舞いを変えたり、実行時に動的に名前空間をプロパティと結びつけて可視/不可視をコントロールすることができます。

namespace の宣言

namespace はパッケージ内とクラス内で定義することができます。定義方法は基本的に変数の定義と同様です。

SampleClass.as:
package sample {
  public class SampleClass {
    // クラス内に名前空間を定義
    public static namespace myNSinClass;
  }
}
 
myNSinPackage.as:
package sample {
  // パッケージ内に名前空間を定義
  public namespace myNSinPackage;
}

パッケージレベルで public な名前空間を宣言するときは単独のファイルに記述します。public なリソースの宣言は一ファイルに付き一つのルールが適用されるためです。(EcmaScript プロポーザルによれば、クラス内の名前空間の宣言は static にする必要があるはずですが、現バージョンではインスタンス変数としても宣言できてしまいます。これは将来のバージョンでは修正されるものと思います)

名前空間には別名を作成したり明示的に値を与えることもできます。

// 匿名の名前空間を定義
public namespace myNS1;
// myNS1 の別名を定義
public namespace myNS2 = myNS1;
// 明示的に値を指定して名前空間を定義
public namespace myNS3 = "http://www.sample.com/namespace";

修飾名の使用

属性を定義する際に属性が所属する名前空間を指定するには、以下のように記述します。

public static namespace myNS1;
public static namespace myNS2;
myNS1 var foo:int = 0;
myNS2 var foo:String = "zero";

この例では foo という名前の属性が2つ定義されていますが、それぞれ別の名前空間に属しているため異なる属性として区別されます。参照する際は以下の形式で修飾名を記述します。

trace(myNS1::foo); // 0 が出力される
trace(myNS2::foo); // zero が出力される

また、属性定義時に複数の名前空間を指定することもできます。この場合、属性の実体はあくまで一つです。属性の別名を定義したと考えると良いでしょう。

myNS1, myNS2 var foo:int = 0;
trace(myNS1::foo); // 0 が出力される
trace(myNS2::foo); // 0 が出力される

属性の宣言時、namespace を使って定義された名前空間の指定はクラスのトップレベルの属性のみに適用可能です。メソッド内のローカル変数定義等には使用できません。

public class SampleClass {
  public static namespace myNS1;
 
  myNS1 var foo:int = 0; // OK
  public function myFunction():Void {
    myNS1 var bar:int = 0; // NG
  }
}

Posted by ackie at 05:44 PM | Comments (0)

January 06, 2006

静的なプロパティ

クラスには静的なプロパティとインスタンスプロパティを定義することができます。インスタンスプロパティはその名のとおりインスタンス毎に存在するプロパティですが、静的プロパティはインスタンスと関連付けられることは無く、代わりにクラスに一つだけ存在するクラスオブジェクトと結び付けられます。

静的変数

変数を宣言する際 static をつけると静的変数の宣言になります。

public static var myStaticVar:int;

静的変数はそれを宣言しているクラスまたはそのサブクラスからは直接参照することができます。

public class BaseClass {
  public static var myStaticVar:int;
  // クラス内で宣言されている静的変数にアクセス
  public function myBaseFunc():Void {
    myStaticVar = 0;
  }
}
 
public class ChildClass extends BaseClass {
  // 親クラスの静的変数にアクセス
  public function myChildFunc():Void {
    myStaticVar = 1;
  }
}

しかし、静的変数は継承されません。従って外部のクラスからは宣言されているクラスオブジェクトを通じてのみアクセスすることができます。

trace(BaseClass.myStaticVar);  // OK
trace(ChildClass.myStaticVar); // NG

継承されないということは、サブクラスに同じ名前で異なる型の静的変数を定義するのも可能ということです(クラスを使う側はちょっと混乱しそうですが)。また、静的変数と同名のインスタンス変数を定義することもできます。ますます混乱しそうなので、この辺りの説明は省略することにします。

静的関数

関数宣言時に static をつけると静的関数の宣言になります。

public static function myStaticFunction():Void;

もう何回も書いているのでそろそろしつこくなってきた感もありますが、関数もオブジェクトですから上記のセクションでの説明がそのまま当てはまります。補足しておきたい点は、

  1. 静的関数内からは静的に宣言されたプロパティしか参照することができません。
  2. 静的関数は継承されないので、サブクラスで同名の関数を宣言する場合でも override はつけません。シグニチャが一致する必要もありません。(むしろ関数である必要もありません)

Posted by ackie at 06:38 PM | Comments (0)

December 29, 2005

Function/関数/メソッド

今回は、関数に関連するタームの整理です。

  1. Function
    ActionScript では関数もプロパティの一種ですが、関数として認識されるオブジェクトの型の名前が Function です。Function 型のオブジェクトは引数を渡して処理を呼び出すことができます。
  2. 関数
    Function 型のオブジェクトの定義やそのインスタンス全般を指します。
  3. メソッド
    クラス定義の一部としてある関数定義や実行時にオブジェクトから呼び出し可能な関数オブジェクトを指します。別の見方をすると、クラス定義の外で定義される関数やオブジェクトから呼び出すことのできない状態の関数が有るということになります。

それでは、皆様良いお年を。

Posted by ackie at 01:14 PM | Comments (0)

December 28, 2005

コンストラクタ (と Singleton)

コンストラクタはクラス名と同じ名前を持つ関数で、インスタンスを初期化する際に呼び出されます。コンストラクタは初期化時の定型処理を記述するのに使われますが、特に定義されていなくても文法上の問題は有りません。

class SampleClass {
  // コンストラクタ定義
  public function SampleClass() {
    // 初期化処理の記述
  }
}

ActionScript では基本的に関数のオーバーロードができないのに倣って、コンストラクタも一つしか定義することができません。コンストラクタを定義する際は、とりあえず引数無しにするか、引数を持つ場合はデフォルト値を指定するというケースが多くなりそうです。

もう一つのコンストラクタの特徴は、返り値の型を指定しないことです。また、AS3 ではコンストラクタのアクセス修飾子として public のみが指定できます。

Singleton

コンストラクタのアクセス修飾子が public のみだと、いくつかのデザインパターンの使用が難しくなります。そこで、シングルトンパターンの実装例を紹介しておきます。(見て分かるようにあまりきれいといえるものではありません)

package pattern {
 
  public class Singleton {
    private static var instance:Singleton;
 
    // コンストラクタ定義
    // 引数が PrivateClass のインスタンス
    public function Singleton(x:PrivateClass) {
    }
 
    // Singleton クラスのインスタンスを返す関数
    public static function getInstance():Singleton {
      if(Singleton.instance == null) {
        Singleton.instance = new Singleton(new PrivateClass());
      }
      return Singleton.instance;
    }
  }
  /* 以下コメントアウト (2006/03/09 追加)
  // プライベートクラスの定義
  private class PrivateClass {
  }
  */
}
// プライベートクラスの定義 (2006/03/09 追加)
class PrivateClass {
}

上記の Singleton クラスはコンストラクタの実行に private クラスへのアクセスが要るため、他のクラスからは引数を正しく指定できません。とりあえず new Singleton() を呼んだとしても、引数の数が違うためコンパイルエラーが出ます。以下は使用例です。

package {
  import pattern.Singleton;
 
  public class SingletonTest {
    public function SingletonTest() {
      var singleton:Singleton = Singleton.getInstance();
    }
  }
}

Posted by ackie at 07:54 PM | Comments (6)

December 27, 2005

get と set

get と set は特別な関数を定義するために予約されている名前です。オブジェクトのカプセル化のため属性に直接アクセスする代わりに関数を定義したい、という人のための機能です。 他の言語では getXXX() とか setXXX() とかいう関数を定義したりすると思いますが、ActionScript では少し流儀が異なります。

下が、get 関数と set 関数それぞれを定義しているサンプルです。

class AccessFuncs {
  // private のプロパティを宣言
  private var privateProp:String;
  // get 関数を定義 
  public function get publicProp():String {
    return privateProp;
  }
  // set 関数を定義
  public function set publicProp(val:String):Void {
    privateProp = val;
  }
}

サンプルコード内で public な get および set の2つの関数を定義する際、get/set それぞれの後にスペースをはさんで、ある名前 (ここでは publicProp) を指定しています。 そして、get 関数では private なプロパティである privateProp の値を返し、set 関数では逆に渡された値を privateProp に代入しています。

さて、このような宣言をすると、以下のような使い方ができるようになります。

var myAccess:AccessFuncs = new AccessFuncs():
myAccess.publicProp = "hello";
var msg = myAccess.publicProp;
trace(msg);   // hello が出力される

上記のコード内で publicProp が属性のように扱われているのが分かると思います。実際には、2行目で "hello" を publicProp に代入する際には publicProp に対する set 関数が呼ばれ privateProp に値が格納されています。そして、3行目で publicProp を参照する際には同様に get 関数が呼ばれ privateProp の値が返されています。しかし、クラスの利用者は publicProp という属性があるかのようにコードを書くことができます。

これだと、とりあえず属性を定義しておいて後からアクセッサを定義すればいいや、というずぼらなこともできそうですが、(そんな人はいませんよね...)アクセッサとして使われる名前と実体である属性の名前は別にする必要があるので、結局クラス内部のコードの書き直しが発生しそうです。

get と set はどちらかだけでも構いません。その場合、参照専用または 更新専用のプロパティとして属性を公開することとなります。

Posted by ackie at 10:05 PM | Comments (0)

December 26, 2005

関数の引数

引数のデフォルト値

AS3 では関数宣言時に引数のデフォルト値を指定することができます。デフォルト値の無い引数と有る引数が存在する場合には、デフォルト値の無い引数を引数の列の最初にまとめます。デフォルト値として使用できる値は、コンパイル時に既知の定数のみです。

function sampFunc(x:int, y:int, z:int = 1) {
  // 呼び出し時に指定がなければ z の値は 1 になる
}
sampFunc(1, 0);

デフォルト値のある引数は呼び出し時に省略可能になります。通常は意識する必要のない引数に使用するとよいでしょう。

argument オブジェクト

デフォルト値の指定されていない引数を省略して関数を呼び出そうとするとコンパイラーがエラーを返します。また、宣言されているより多くの引数を指定して呼び出す場合もコンパイルエラーになります。ですが、どちらの場合も実行は許されます。(型の不一致は当然実行時エラーになりますが)

その際、引数にアクセスするためには arguments というオブジェクトを使用することができます。これは宣言無しで関数内で参照できるオブジェクトで、渡された引数の配列と呼び出し元のオブジェクト (arguments.callee) をプロパティとして持っています。ただし、引数名に arguments が使用されている関数内では arguments はその引数を指し、arguments オブジェクトは使用できなくなります。

arguments オブジェクトは以下のような再帰関数の実現に便利です。

var factirial:Function = function(x:int):int {
  if (x == 0) {
    return 1;
  } else {
    return (x * arguments.callee(x - 1));
  }
}

ところで、引数の数が不定の場合はコンパイルエラーを無視して関数を呼び出さなければならない、というのもちょっと気持ち悪いものです。そこで、AS3 には、arguments に代わる次のような機能が用意されています。

「...」 引数

AS3 では最後の引数として 「... + 任意の名前」 を指定すると、定義された数以上の引数を渡すことができるようになります。明示的に名前の指定されていない分の引数は、「任意の名前」で指定された名前を持つ配列として使用できます。下の例では、rest という配列に 2 と 3 が格納されます。

function sampFunc(x:int, ... rest) {
  if (rest.length > 0) {
    trace(rest[0];
  }
}
// 2 が出力される
sampFunc(1, 2, 3);

arguments オブジェクトと違って、この方法であればコンパイラーも文句を言いません。関数定義からも引数の数が不定であることが分かります。事前に引数の数が特定できない場合は 「...」 の使用をお勧めします。なお、「...」 が指定されていると arguments オブジェクトは使用できません。

Posted by ackie at 08:35 PM | Comments (0)

December 22, 2005

関数の定義

ActionScript のオブジェクトは Java や C++ と少し違う点があります。それは、オブジェクトがプロパティの集合であるという点です。プロパティには関数も含まれます。そのため実行時に関数を代入することも可能です。また、関数も他のオブジェクトと同様に、固有のプロパティをやメソッドを持つことができます。

関数の定義

AS3 でのクラス定義の例を下に示します。ここでは、関数は function ステートメントを使用して定義されています。

class SampleClass {
  var sampleVar:String;
  function sampleFunction():Object {
  }
}

さて、関数もプロパティなのですから、以下のような記述が可能です。

class SampleClass {
  var sampleVar:String;
  var sampleFunction:Function;
}

上の例では、Function 型のプロパティ sampleFunction を宣言しています。冒頭に書いたように、このような宣言をしておくと実行時に Function オブジェクト(正しくは、Function オブジェクトへのリファレンス)を sampleFunction プロパティに代入することができます。

そのため、function 式を使って、以下のような形で関数定義をすることも可能です。(注:この function キーワードはエクスプレッションで、関数定義をする function ステートメントとは別のものです。)

class SampleClass {
  var sampleVar:String;
  var sampleFunction:Function = function ():Object {
  }
}

一番最初に紹介した形式での関数定義の方が見やすいと思いますし、クラス定義にこの書き方はあまりお勧めしません。

関数のオーバーライド

クラスを拡張して別のクラスを定義するとき関数を上書きして定義する場合には override を付けます。override は Function 型のプロパティがメソッドのように扱われること、すなわち上書きされる関数とシグニチャが同一になることを保障します。

class ExtendedClass extends SampleClass {
  var sampleVar:String;
  override function sampleFunction():Object {
  }
}

継承については、また別途詳しく説明します。

Posted by ackie at 07:00 PM | Comments (0)

December 15, 2005

package

AS2 でも package の機能はありましたが、その役割は基本的にクラス名の重複を防ぐことでした。AS3 の package はアクセススコープの単位にもなります。

package とアクセス修飾子

AS3 では以下のようにして定義するクラスが含まれるパッケージ名を宣言します。以下の例では、sample パッケージ内に SampleClass クラスを定義しています。このときクラスの完全限定名は sample.SampleClass になります。

package sample {
  import othersample.OtherClass;
  class SampleClass {
    function sampleFunction() {}
  }
}

他のパッケージに定義されたクラスを使用するには、import を使用します。上の例のように明示的にクラス名を指定する代わりに * を使うこともできますが、一つ一つ指定した方が性能的には良い結果になるようです。

さて、AS3 では以下の4つのアクセス修飾子が使えます。AS2 では private が AS3 の protected に相当するものでした。注意しましょう。(ちなみに protected は ECMAScript 標準ではサポートされていません。本来、アクセススコープの管理は namespace を使うという考え方のようです。)

public     // どのクラスからでもアクセス可
internal   // 同一パッケージ内のクラスからのみアクセス可
protected  // 自身とサブクラスからのみアクセス可
private    // 外部からのアクセスなし

最初に示したサンプルコードの様に特にアクセス修飾子が指定されていない場合、AS3 では internal が適用されます。その他の修飾子を指定したい場合には、クラス名や関数名の前に public 等をつけます。

as ファイル

AS3 のクラスを定義したファイル名はクラス名に .as の拡張子をつけたものにします。また、パッケージを指定した場合は、パッケージ名と同じディレクトリ階層下にファイルを置きます。この記事の最初のサンプルは sample\SampleClass.as の内容というわけです。これは Java とおんなじですね。

一つのファイル内に複数のクラスを定義することもできます。ただし、public なリソースの定義はファイル内に一つだけ可能で、他のクラス定義は同一ファイル内のみでの参照になります。Java と少し違うのは、public 以外の定義に private をつけることです。また、Java のメンバクラスに相当する機能はありません。

package sample {
  import flash.events.*;
  public class SampleClass {
    public function SampleClass() {
      // コンストラクターを定義
      var innerClass:InnerClass = new InnerClass();
    }
  }
  private class InnerClass {
    // プライベートクラスの定義
  }
}

Posted by ackie at 07:28 PM | Comments (0)

December 14, 2005

SimpleButton クラス

マウスイベントの続きということで、遅ればせながら SimpleButton クラス (livedocs@lab) の説明です。

SimpleButton クラス

SimpleButton は InteractiveObject のサブクラスで、マウスの操作に応じてボタンらしく振舞うことができます。 Flash オーサリングツールでボタンシンボルを作成すると4つのフレームができますが、SimpleButton にもそれぞれのフレームに対応したプロパティが存在します。

public upState : DisplayObject        // アップ(通常)の状態の表示オブジェクト
public overState : DisplayObject      //(マウス)オーバーの状態の表示オブジェクト
public downState : DisplayObject      //(マウス)ダウンの状態の表示オブジェクト
public hitTestState : DisplayObject   // ヒットテスト用のオブジェクト

以上の4つのプロパティに対応するマウス操作の状態それぞれに対して、意図された表示を実現する DisplayObject を設定するのが SimpleButton の基本的な使用方法です。

var myButton:SimpleButton = new SimpleButton();
// 各ステート用のオブジェクトを作成(今回は一つの Shape を共有)
var stateShape:Shape = new Shape();
stateShape.graphics.lineStyle(2, 0x202020);
stateShape.graphics.beginFill(0xFF0000);
stateShape.graphics.drawRect(10, 10, 50, 50);
// ボタンオブジェクトのプロパティを設定
myButton.upState = stateShape;
myButton.downState = stateShape;
myButton.overState = stateShape;
myButton.hitTestState = stateShape;

SimpleButton にはその他に以下のプロパティがあります。

public trackAsMenu : Boolean       // メニューのような使い方をするか
public enabled : Boolean           // ボタンを有効にするか
public useHandCursor : Boolean     // カーソルの形状を手の形にするか

trackAsMenu は他の場所でマウスが押された場合でも click イベントを受け取りたい場合に使用します。通常 click イベントは同一オブジェクト上でマウスを押す/放すという行為をしないとディスパッチされませんが、trackAsMenu を true にしておくと、他のオブジェクト上でマウスを押してそのまま移動してある SimpleButton オブジェクト上でマウスを放した場合にも、その SimpleButton オブジェクトに click イベントがディスパッチされます。メニューを作るときに使いそうな機能ですね。

enabled プロパティはボタンの有効/無効を切り替えます。enabled を false に設定すると、実際マウスを操作しても見た目上は反応しなくなります。ただし、イベントはディスパッチされるようです。

useHandCursor プロパティはデフォルトが true です。通常は特に気にする必要はなさそうです。

Posted by ackie at 08:53 PM | Comments (0)

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)

November 28, 2005

コンパイラのモード設定

ActionScript3 は動的言語ですので、実行時につじつまが合えば型の指定は省いても構わない、という記述方法が可能です。とりあえず単純な例としては、

var forDebug; // 型指定なしの宣言
forDebug = 0; // 数値を代入 trace(forDebug);
forDebug = "1"; // 文字列を代入 trace(forDebug);

のような使い方です。(ちなみに、型宣言をしないと Object 型の変数になります) これはこれで便利な機能でめんどくさいときなどは結構重宝しているのですが、やはりきちんと型指定をしておきたいケースも多々あったりします。その際は、例えば、

var forDebug:String; // String 型を指定
forDebug = 0; // 数値を代入 trace(forDebug);
forDebug = "1"; // 文字列を代入 trace(forDebug);

のように、型指定の変数を宣言することも可能です。ただ、上のサンプルでは型宣言を追加したものの、コードの2行目で数値を代入しているため、そこで型の不一致が起き正しく実行できないはずです。ためしに実行してみると、”Type Coersion failed: cannot convert 0 to String” というランタイムエラーが表示されます。当たり前のようにも見えながら、このランタイムエラーは ActionScript3 からの待ちに待った新機能です。今までのようにどこかで静かに処理が失敗しているという状況からはようやく開放され、開発生産性の向上に大きく貢献してくれそうです。(ランタイムエラーが表示されるのはデバッグプレーヤのみで、一般のユーザにはこのようなメッセージは表示されません。)

しかし、どうせ実行時にエラーになると分かっている件については、コンパイル時に検出してくれたほうが簡単に対応できます。そのため、コンパイラは strict モードが選択できるようになっています。コンパイル時、引数に -strict を付けることでコンパイラは型チェックを行うようになります。

FlexBuilder2 では strict モードがデフォルトの設定になっています。これを変更するには、画面左のナビゲータパネル内で対象のプロジェクト名を右クリックし、表示されたメニューから Properties を選んでポップアップウインドウを表示します。ウインドウ内の左のメニューから ActionScript Compiler を選ぶと下の画面になります。

compiler options

このパネルで、Enable compile-time type checking (-strict) のチェックを外してOKを押すと、コンパイル時に型チェックが行われなくなります。

また Enable warnings (-coach) は AS2 からのマイグレーションに関連する警告(シンタックスの変更など)を表示させるためのオプションです。分かって使っている人にはちょっとうざいメッセージが出力されますが、とりあえずチェックをかけたいときにはなかなか便利です。

なお、これらの設定はプロジェクト単位で有効です。

Posted by ackie at 08:34 PM | Comments (0)

November 21, 2005

flash.display パッケージのとりあえずのまとめ

これまで紹介してきたクラスは flash.display パッケージに含まれているクラスの主要メンバーです。少し乱暴な言い方をしてしまうと、flash.display パッケージは AS2 での MovieClip が持っていた役割を再編成したものと言えるでしょう。各クラスの役割も明確になり、使いやすくなったのではないかと思います。AS2 には無かったコンセプトがいくつも出てきましたが、AS3 でプログラミングする上では外せない部分ですので実際にいろいろと試してみていただければと思います。まだアルファ版のため ”本当はこう動くはずじゃないかな?” という期待と違う動きをすることもしばしばあります。どうもバグらしいと思ったら時間のあるときで構いませんのでレポートしてください。英語が面倒な方は、当面私宛に送っていただいてもOKです。

説明だけでは分かりにくかったかも、ということで最後に簡単なサンプルコードを載せておきます。(HelloWorld です。ベタで済みません。) サンプルコードでは、コンストラクタがいわゆる main() 関数の代わりをしています!ActionScript3 ではスクリプトだけで Flash アプリケーションが出来てしまうのですね。もう fla ファイルとか mxml ファイルとかの単なる補助ではありません。ここに AS3 の可能性をちょっと感じてしまったりします。

package {
import flash.display.Sprite;
import flash.display.TextField;
import flash.text.TextFormat;
import flash.events.Event;
import flash.events.EventType;
import flash.geom.Point;
import flash.geom.Rectangle;
// Sprite のサブクラスを作成 public class HelloWorld extends Sprite {
// プライベート変数を宣言 private var label:TextField; private var target:Point = new Point();
// コンストラクタ public function HelloWorld() { createLabel(); addEventListener(EventType.ENTER_FRAME, enterFrameListener); }
// TextField に Hello World を表示させる private function createLabel():Void { label = new TextField(); label.text = "Hello World";
var format:TextFormat = new TextFormat(); format.font = "Verdana"; format.color = 0xFF0000; format.size = 16; label.setTextFormat(format);
// ディスプレイリストに追加 addChild(label); }
// enterframe のイベント処理 public function enterFrameListener(event:Event):Void { var current:Point = label.getBounds(this).topLeft; if (Point.distance(target, current) < 1) { resetTarget(); } label.x += (target.x - label.x) / 8; label.y += (target.y - label.y) / 8; }
// ターゲット座標の設定 private function resetTarget():Void { target.x = Math.random() * stage.stageWidth - 100; target.y = Math.random() * stage.stageHeight - 10; } }
}

Posted by ackie at 10:40 PM | Comments (3) | TrackBack

November 07, 2005

Stage クラス

Stage クラスは Flash コンテンツの描画領域に対応するクラスです。(livedocs@lab) DisplayObject は Stage のインスタンスの領域上に表示されます。AS2 での Stage はグローバルオブジェクトでしたが、AS3 では DisplayObject.stage プロパティからアクセスします。

Stage にはいくつかの便利なイベントがあり、Flash Player と OS 間のフォーカス情報などを知ることが出来ます。

activate    : Flash Player が OS からフォーカスを貰ったとき  
deactivate  : Flash Player が OS のフォーカスを失くしたとき 
mouseLeave  : マウスポインターが Stage の領域外に移動したとき
resize      : scaleMode=noScale で Stage の大きさが変更されたとき

Stage は DisplayObjectContainer のサブクラスですが、多くのプロパティやメソッドは Stage には適用することが出来ません。(例えば Stage を回転させるとか) これらの機能を誤って使用すると例外が投げられるようになっています。 該当するプロパティは alpha, blendMode, cacheAsBitmap, contextMenu, filters などです。一方 Stage 固有のプロパティも存在します。例えば、以下のようなものがあります。

public align : String      // ステージの配置(StageAlign.TOP,StageAlign.LEFT,...)
public quality : String    // 描画クオリティ(StageQuality.LOW,
StageQuality.BEST,...)
public scaleMode : String  // 使用するスケールモード(StageScaleMode.NO_SCALE,...)

DisplayObject.root プロパティについて

DisplayObject の root プロパティは、自身が含まれる SWF の root を指します。Loader により読み込まれた SWF 内の DisplayObject からは、root は読み込まれた SWF のトップの DisplayObject です。(AS3 では、読み込まれた SWF は Loader の子オブジェクトになるのでした。従って、読み込んだ側の Loader.content と読み込まれた SWF の DisplayObject.root は同じオブジェクトを指します。)

Stage も DisplayObject のため root プロパティを持ちますが、これはメインの root を指しています。従って、任意の DisplayObject からは DisplayObject.stage.root のようにして、メインの root を参照することができます。

Posted by ackie at 09:15 PM | Comments (0)

November 04, 2005

Splite クラスと MovieClip クラス

Sprite クラス

Sprite は、基本的には DisplayObjectContainer をそのまま具象クラス化したものと見做せるでしょう。(livedocs@lab) 子オブジェクトの管理やユーザーインタラクションの実現など、Flash コンテンツを開発する上でコアになる機能を提供します。 (DisplayObjectContainer + InteractiveObject + DisplayObject

DisplayObjectContainer から追加されている主な機能ははドラッグ&ドロップ操作への対応くらいです。下記はそのメソッドです。

public startDrag(lockCenter:Boolean = false, bounds:Rectangle = null) : Void
public stopDrag() : Void

Sprite のサブクラスである MovieClip よりも軽いので、AS3 では開発の中心となるクラスと位置づけられています。UIComponent のベースクラスも AS3 からは Sprite に変更されました。(というわけで UIObject クラスも必要なくなりました。) また、なにより new Sprite() と書けるのが嬉しいところです...というのは、AS2 を書いたことのない人には関係ない話でしたね。

AS3 での典型的なクラスのコーディングは次のような感じです。

// 1.Sprite (または MovieClip)のインスタンスを生成
   var foo:Sprite = new Sprite();
// 2.必要な DisplayObject を Sprite のディスプレイリストに追加 var myDo:DisplayObject = new myDisplayObject(); foo.addChild(myDo);
// 3.イベントに対するコールバック関数を定義する myDo.addEventListener(MouseEventType.MOUSE_UP, onMouseUpEvent);

MovieClip クラス

MovieClip は Sprite にタイムラインとシーン機能が追加されたものです。(livedocs@lab) スクリプトのみで Flash コンテンツを制作する場合にはあまり使う機会はないかもしれません。実際、特に理由がない限りは親クラスの Splrte を使用するようにしましょう。

MovieClip もやっと ActionScript オブジェクトになり、普通に new MovieClip() が書けるようになりました。その代わりに MovieClip のインスタンスを生成する方法はコンストラクタのみになりました。createEmptyMovieClip や duplicateMovieClip といったメソッドはありません。

Posted by ackie at 07:13 PM | Comments (0)

November 02, 2005

Loader クラス

Loader クラス(livedocs@lab)は、SWF ファイルを読み込んだり、JPEG, PNG, GIF 等のイメージデータを読み込むのに使います。Loader のメソッドは以下のとおりです。

public load(request:URLRequest) : Void    // 指定された URL からロード
public unload() : Void                     // 読み込んだオブジェクトの削除
public close() : Void                      // 実行中のロードをキャンセル
public loadBytes(bytes:ByteArray) : Void  // 引数からデータをロード

読み込まれたオブジェクトは Loader の子オブジェクトになります。Loader.content プロパティからこれを参照することが出来るようになっています。Loader は DisplayObjectContainer のサブクラスですが、子オブジェクトを一つしか持つことが出来ない特殊なクラスです。このため子オブジェクトを追加/削除する類のメソッドを呼び出すと例外が投げられます。

loadByte メソッドはちょっと面白いメソッドで、メモリ上のバイナリデータから SWF や GIF, JPEG, PNG のオブジェクトを作成することができます。

Loader のイベントとしては、ロードの状況や終了を知らせるイベントがサポートされています。

open           : 処理が開始された
progress       : ロード処理を実行中
complete       : ロードが完了した
init           : ロードした SWF が初期化された

securityError や unload といった新しいイベントも追加されました。

読み込み中のオブジェクトの情報は Loader.loadeeInfo プロパティからも得ることが出来ます。このために LoaderInfo というクラスが専用で提供されています。(livedocs@lab) Loader.loadeeInfo プロパティは読み込まれる側からも共有されていて、読み込まれた SWF からは DisplayObject.root.loaderInfo プロパティ経由でアクセスできます。このとき、読み込まれた SWF は、読み込む際に URL パラーメタが指定されていると loaderInfo.arguments プロパティから取得できるようになっています。

AS1/2 では、MoviClipLoader クラスや MovieClip.loadMovie メソッドが同じ目的に使用されていましたが、AS3 では Loader に置き換えられています。

少し話しは外れますが、AS3 からは LoadVars も Loader のサブクラスの URLLoader (livedocs@lab)で置き換えられました。併せて AS2 の XML は通信機能が省略された上で XMLDocument (livedocs@lab)というクラスに変更されています。(AS3 の XML は新しいクラスです。これについては別途詳しく解説する予定です。)

Posted by ackie at 08:35 PM | Comments (3)

November 01, 2005

DisplayObjectContainer クラス

次は、InteractieObject のサブクラスで「コンテナ」機能が追加された抽象クラスです。

DisplayObjectContainer クラス

DisplayObjectContainer クラス(livedocs@lab)は、その名のとおり DisplayObject のコンテナ、つまり他の DisplayObject を子オブジェクトとして持つことの出来るクラスです。一方 DisplayObjectContainer 自身も DisplayObject ですから子オブジェクトになることができます。つまり DisplayObject のツリー構造を作ることが出来ます。(末端ノード以外は DiplayObjectContainer である必要があります) Flash の経験者であれば MovieClip が入れ子になっているものをイメージするとよいでしょう。

このように、DisplayObectContainer は画面に表示されたオブジェクトをまとめて管理するのに使われます。DisplayObjectContainer (およびそのサブクラス)が子オブジェクトを管理する仕組みをディスプレイリストと呼んでいます。

DisplayObjectContainer に子オブジェクトを追加/削除するには、それぞれ以下のメソッドを使います。

public addChild(child:DisplayObject) : DisplayObject
public removeChild(child:DisplayObject) : DisplayObject

この際、後に追加されたものほど上に表示されます。子オブジェクトはインデックスで管理され、インデックス 0 が最背面のオブジェクトになります。addChild メソッドは子オブジェクトを追加する際、そのオブジェクトのインデックスが最大値になるように設定する訳です。追加された子オブジェクトからは DisplayObject.parent で親のコンテナを参照することができます。

任意のインデックスに子オブジェクトを追加したい場合には、以下のメソッドを使います。

public addChildAt(child:DisplayObject, index:int) : DisplayObject

また、次のメソッドを使うと既存の子オブジェクトのインデックスを変更することが出来ます。

public setChildIndex(child:DisplayObject, index:int) : Void

AS1/2 とは違い、表示の重なりの制御に depth を使用しなくなりました。

Posted by ackie at 08:44 PM | Comments (0)

October 31, 2005

InteractiveObject クラス

DisplayObject クラスのサブクラスとして Bitmap と Shape を紹介しましたが、次は、もう一つのたぶん一番良く参照されるサブクラスです。

InteractiveObject クラス

DisplayObject クラスにユーザーインタラクションのためのイベント等を追加した抽象クラスが InteractionObject クラス(livedocs@lab)です。概念的には、画面に表示されて、マウスやキーボード入力に反応するオブジェクトをモデル化したものと考えることができるでしょう。(ということは、Bitmap や Shape は、ユーザーが直接操作できないわけです。) InteractiveObject の直接のサブクラスには SimpleButton や TextField などがあります。この InteractionObject クラス(のサブクラス)はフォーカスの制御の単位にもなります。

これにより、AS1/2 では何種類もあったユーザーイベントの扱いが一つのクラスに集約され、すっきりと統一されました。AS2 の Key や Mouse といったグローバルオブジェクトによるユーザーイベントのハンドリングは AS3 にはありません。

イベントモデル自体も、新しくなっています。AS3 の新しいイベントモデルについては、重要なトピックですので別の機会に改めて詳しく説明したいと思っています。今回はとりあえず主なイベントの種類だけざっと触れておきます。

InteractiveObject がサポートするイベントは、AS2 では MovieClip や Button が持っていたイベントを引き継ぐものや、

mouseDown       : マウスボタンを押した
mouseUp         : マウスボタンを離した
keyDown         : キーを押した
keyUp           : キーを離した
focusIn         : オブジェクトがフォーカスされた
focusOut        : オブジェクトがフォーカスを失くした

V2 UIComponent のイベントと同じもの、または新規追加(ダブルクリックなど)されたものがあります。

click           : マウスがクリックされた
doubleClick     : マウスがダブルクリックされた
keyFocusChange  : キー操作でフォーカスを変えようとした

また、InteractiveOjbect には、右クリックをしたときに表示されるコンテキストメニューを InteractiveObject.contextMenu プロパティに設定することが出来ます。ただ、相変わらずトップレベルのオブジェクトにしか有効ではないようです。

Posted by ackie at 06:17 PM | Comments (0)

October 28, 2005

Bitmap クラスと Shape クラス

Bitmap クラス

Bitmap クラスはビットマップ画像を表示するのに使用されます。(livedocs@lab) Bitmap クラスの役割は表示位置やフィルターの種類の指定などの表示状態を指定することで、実際のビットマップデータは Bitmap.bitmapData プロパティから参照される BitmapData クラスのインスタンスが持ちます。Bitmap はあくまで容れ物ということです。

BitmapData クラスについては Flash8 から追加されたものと基本的に同じです。(livedocs@lab) 描画オブジェクトのスナップショットを取ったり、様々な加工を施すことができます。AS1/2 では、MovieClip に attach して使用していましたが、AS3 では Bitmap が MovieClip の代わりをします。

BitmapData のインスタンスは複数の Bitmap オブジェクトから共有できるため、画面にたくさんのビットマップ画像を表示しても、メモリを効率的に使用できるようになっています。

Shape クラス

Shape クラス(livedocs@lab)はベクターグラフィックの表示が担当です。Bitmap クラスと同じように、実際のデータは Shape.graphics というプロパティに持ちます。このプロパティのデータ型は Graphics クラスです。

Graphic クラス(livedocs@lab)は以下のように描画用の API を持っています。これらの API は、AS1/2 では MovieClip のメソッドとして実装されていましたが、AS3 では専用のクラスが用意されました。

 public moveTo(x:Number, y:Number) : Void 
 public lineTo(x:Number, y:Number) : Void 
 public beginFill(color:uint, alpha:Numer = 1.0) : Void 
 public clear() : Void 

また、新しく便利な API もいろいろと追加されています。

 public drawRect(x:Number, y:Number, width:Number, height:Number) : Void 
 public drawCircle(x:Number, y:Number, radius:Number) : Void 

Posted by ackie at 07:24 PM | Comments (0)

October 27, 2005

DisplayObject クラス

ActionScript3 では、API が大幅にリパッケージングされています。これは、描画機能が集約された重たい MovieClip への依存からの脱却が目的の一つで、描画関連の機能にも新しい考え方やクラスが導入されています。AS3 を使うにはこれらのクラスを理解しなくてはなりません。そこで、AS3 の新しいモデルについて、少し説明をしたいと思います。まずは少し抽象的な話から始めます。

注:以下の内容は、パブリックアルファ版に基づくもので、製品版までには変更されることがあります。

DisplayObjct クラス

DisplayObject は ActionScript3 から新しく導入されたクラスで、画面に表示される全てのオブジェクトのベースとなる重要なクラスです。プロパティを見ると、以下のような、表示の様子を指定する基本的なプロパティを持っていることが分かります。(livedocs@lab)

x            // インスタンスのX座標
y            // インスタンスのY座標
height       // インスタンスの高さ
width        // インスタンスの幅
alpha        // インスタンスのアルファ値

ActionScript1/2 では、これらのプロパティは MovieClip 等に属していましたが、AS3 では DisplayObject のプロパティになっています。また、名前から '_' (アンダースコア)がなくなっていることにも注意してください。

DisplayObject のプロパティには、Flash8 から追加された高度な表現を指定するものもあります。

scale9Grid    // インスタンスのX座標
blendMode     // ブレンドモードを指定
filters	      // フィルターオブジェクトの配列
cacheAsBitmap // ビットマップ形式で表示データを保持するフラグ

このように、DisplayObject は表現を指定するのに必要な機能が集められたクラスです。一方インタラクションを提供する機能は含まれていません。また、抽象クラスなので直接インスタンスかすることは出来ません。

DisplayObject の主なサブクラスとして BitMap や Shape があります。それぞれ

  • Bitmap : ビットマップデータを表示する
  • Shape : ベクターグラフィックスを表示する

という目的で使用されます。これらのクラスは MovieClip より軽量であるため、描画処理の高速化に貢献することが期待できます。

Posted by ackie at 08:47 PM | Comments (0)