akihiro kamijo: ActionScript 3.0 Archives

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 および Namespa