ConvolutionFilterで遊ぶ その3

ピクセル変換の過程を追いかけてみる

前回のエントリーで紹介したツールを使って、ConvolutionFilterの詳細を追いかけてみたいと思います。<<ここ>>にアクセスして、テスト用画像を開いて下さい。
黒い背景の中央に1ピクセルの白いピクセルが表示されていると思います。
この画像を使ってConvolutionFilterによるピクセル変換を見ていきたいと思います。
(Convolutionでどのような計算が行われているかはConvolutionFlterで遊ぶ その1を参照)

さて、いま、行列は以下のようになっています。
0 0 0
0 1 0
0 0 0

この行列を使ってConvolutonFilterを適用する事の意味を考えてみます。
ターゲットとなるピクセル(以下、ターゲットピクセルと表記します)の周辺にある8ピクセルはずべて0です。
周辺にどんな色のピクセルがあったとしても、0倍されるので結果は0です。
言い換えると、ターゲットピクセルの周辺のピクセルは全く考慮しないということになります。
一方、ターゲットピクセルに対応する行列の数値は1です。
自身のピクセルの強さ(色)を1倍するわけですから、ターゲットピクセルは元の色そのままということになります。
つまり、この行列は、なんのピクセル操作もしない行列です。

平行に移動コピー

では、行列を以下のように変更してみます。
何が起こるでしょうか?
Kernelの数値を変更して実際に結果を確認してみて下さい。
0 0 0
0 0 1
0 0 0

ピクセルを1倍してそのまま表示するという意味では、先ほどの「何もしない行列」と同じですが、
1倍するピクセルがターゲットピクセルの右隣になっています。
つまり、ターゲットピクセルを「ターゲットピクセルの右隣のピクセルにそのまま置き換える」という意味になり、
その結果、画像全体が1ピクセル左に移動します。
(メインビューにマウスカーソルをのせて、拡大画像で確認できます。)
convEx1.jpeg

両側に移動コピー

次はターゲットピクセルの左の数値も1にしてみましょう。
0 0 0
1 0 1
0 0 0

今度はターゲットピクセルの右隣だけでなく、左隣のピクセルもコピーします。
左方向の平行移動と右方向の平行移動を重ね合わせたようなイメージです。
convEx2.jpeg

4方向に移動コピー

ということは、以下のような行列は、
0 1 0
1 0 1
0 1 0

予想通りこうなります↓。
このように1倍する行列はすごくわかりやすいですね。
convEx3.jpeg

ガウスぼかし

1倍する行列はこのくらいにして、つぎはガウスぼかしについてみていきます。
Presetから「gauss」を選択して下さい。
convEx4.jpg

行列が次のように変更されると思います。
0.03 0.11 0.03
0.11 0.44 0.11
0.03 0.11 0.03

これも、わりと行列から結果が推測しやすいと思います。
行列をみると、ターゲットピクセルの色を44%に薄めて、薄まった分を周辺のピクセルのから少しずつ足してるのがわかります。
イメージとしては、ターゲットピクセルを周りの色と馴染ませる感じですね。
convEx14.jpg

エッジ検出

エッジ検出はややこしいのですこしずつ理解していきます。
<<ここ>>にアクセスして、次のテスト画像を開いて下さい。
黒い背景に白い四角形の画像が表示されます。

行列を次のように変更してみて下さい。
0 0 0
-1 1 0
0 0 0

四角形の左辺だけが抽出されました。おもしろいですね。
convEx6.jpg
何が起こっているのか考えてみます。
メインビューにカーソルを移動して、四角形の左上角のピクセルをポイントして下さい。
以下のようにその部分を拡大してみる事ができます。
convEx7.jpeg
ターゲットピクセルを、四角形の左上角のピクセルにして考えてみます。
まず、行列の数値0は無視していいのを思い出して下さい。(0倍なので計算に影響しない)
そうすると、考慮するのはターゲットピクセル(行列の1)とその左隣のピクセル(行列の-1)です。
また、ターゲットピクセルの左隣のピクセルは黒(0)です。
黒(0)を何倍しても0なのでこれも無視できます。

つまり、この場合、考慮するのはターゲットピクセルのみということになります。
結果、ターゲットピクセルを1倍してそのままです。
次に、今見たピクセルの右隣のピクセルをターゲットピクセルにして考えてみます。
convEx8.jpeg
行列から、考慮するのはターゲットピクセルとその左隣のピクセルです。
今度は先ほどと違い、ターゲットピクセルの左隣のピクセルは白(255)です。
対応する行列の数値は-1。かけ算すると255 x -1 = -255。
これをターゲットピクセル(白255)と足し合わせます。
結果、255 + (-255) = 0 で、もともと白だったターゲットピクセルが黒に変更されました。
ちょっと乱暴にまとめると、
自分(ターゲットピクセル)の左隣がxで、自分もx(x以下)だったら黒にする。そうじゃなければ、なにもしない。
これを言い換えると、ある色が連続して平行に並んでいる場合、その一番左の色を残し他は黒(0)にすると言えます。
この結果、左端の輪郭が抽出されます。

「両側にコピー」でやった事を応用すれば、右辺も抽出できます。
0 0 0
-1 2 -1
0 0 0

convEx9.jpeg

同様に、上辺と下辺を抽出することで、基本的な輪郭検出の行列になります。
0 -1 0
-1 4 -1
0 -1 0

convEx10.jpeg
※ちなみに、この行列を使ったエッジ検出をラプラシアンフィルタというらしい。

エンボス

エンボスは輪郭検出に似ています。
輪郭検出と違うところは、斜め1方向だけにとどめるところです。
0 0 0
0 1 0
0 0 -1

convEx11.jpeg
このままだとただの輪郭ですが、行列の計算時に各値を128加算(オフセット)すると、以下のようになります。
(いまのところ、このツールにはオフセットを設定する機能は省略してあるので、実際には↓のような画面を見ることはできません。。)
convEx12.jpeg
四角形の上辺と左辺に影が落ちてみえるのが不思議ですよね。
なぜ128オフセットしたのに、ここだけ黒(0)なのでしょうか?
四角形の左上角のひとつ斜め左上のピクセルをポイントしてみましょう。
convEx13.jpeg
見た目は黒ですが、計算結果である「new pixel」値が(-255)になっています。
このため、128が加算されてもグレーにはならず黒い影のように見えるというわけです。
それに対し、値が0の黒いピクセルは、オフセットにより128となりグレーになります。
うまいことできてますね。

カラー画像の場合

今回は単純なグレースケール画像で色々見てきましたが、カラー画像になっても理屈は同じだと思います。
R、G、B各チャンネル(グレースケール画像)に対して同じ事が行われるだけです。

ここまでのまとめ

webで検索してもなかなかリソースが少ないConvolutionFilterですが、正直、人文系Flash使いとしては、ロジカルにこれを追いかけるのはこの辺りが限界。数学的バックグラウンドを持っている人は、もっとエレガントに説明してくれるはず。
奇才の登場を待ちましょう。。
次回は、ConvolutionFilterを使ったアニメーションの可能性について探ってみたいと思います。
つづく…

カテゴリー: flash | コメントする

ConvolutionFilterで遊ぶ その2

前回の続きです。
ConvolutionFilterの理解を深めながら遊ぶためのツールを作ってみました。
>>ツールを表示する


画面左上の大きな画像がメインビューです。ここにフィルタの適用結果が表示されます。
mainView.jpg
メインビューの下にあるボタンをクリックすると、ローカルまたはネット上にある他の画像と差し替える事もできます。
(ネット上にある画像を読み込むにはFlashからのアクセスを許可するためのcrossdomain.xmlが設置されている必要があります。)
loadgui.jpeg
メインビューにマウスカーソルを移動すると、カーソル付近の拡大画像が表示されます。
左がフィルタ適用後で右がオリジナル画像です。
中央のピクセルが、いまカーソルがポイントしているピクセルになります。
zoomView.jpeg
オリジナルのピクセルがどういう計算を経て新しいピクセルに変換されるかをExpressionで確認できます。
計算方法については前回のエントリーを参照して下さい。
「use Hex」をチェックすると、数値が16進数表記になります。
expression.jpeg
KernelはConvolutionFilterに使う行列です。
いまのところ3×3の固定になっています。(将来拡張予定)
この数値を変更すると、フィルタの適用結果がメインビューの画像に反映されます。
kernel.jpeg
いくつかの代表的な行列が、あらかじめプリセットとして用意されています。
プリセットから何か選択するとKernelとメインビューに反映されます。
preset.jpeg
Kernelの数値を変更して、気に入ったエフェクトが出来たら、「Add」をクリックするとプリセットに追加されます。
これはSharedObjectに保存されるので、次回ツールを起動したときにもプリセットに表示されます。
プリセットからアイテムを削除したい場合は、Controlキー(MacはCommandキー)を押しながら
プリセット全体をデフォルトにリセットしたい時は「Revert」をクリックします。
add_revert.jpeg
ConvolutionFilterを連続して適用するとどうなるかを実験するにはこのインターフェイスを使います。
▶ボタンをクリックすると、連続してフィルタを適用します。
×ボタンをクリックすると、適用されたフィルタをリセットします。
スライダーでフィルタを適用するインターバルを調節できます。
repeat.jpeg
作業中の画像やカーネルの値等は、URLパラメータとして渡すことができます。
パラメータつきURLが画面下部に表示されているので、このURLを保存しておけば作業の続きが再開できます。
また「Save as Bookmark」をクリックするとブラウザのブックマークとして保存できます。
bookmark.jpeg


次回はこのツールを使って、さらにConvolutionFilterの挙動を探っていきたいと思います。
つづく…

カテゴリー: ActionScript2, flash | コメントする

ConvolutionFilterで遊ぶ その1

Flash8からネイティブに実装されているConvolutionFilterですが、自分はあまり理解していなかったので色々遊んでみました。

ConvolutionFilterで出来る事

ConvolutionFilterを使うとガウスぼかしやエンボス等、いわゆるビットマップエフェクトが可能になるようです。
DisplacementMapFilterと併用すると、Photoshopのフィルターの多くをFlashでシミュレートできるんじゃないかと妄想しています。

ConvolutionFilterとは

ConvolutionFilterは、一言でいうとピクセルの色を変更するフィルターです。
ピクセルの色を変換するという意味ではColorTransformに似ていますが、
ColorTransformは係数のみでピクセルの色を計算、変更するのに対し、
ConvolutionFilterは計算時に、変更対象になるピクセルの周辺のピクセルも考慮します。
この係数は行列を使って定義します。

  ColorTransform Convolution
周辺のピクセル 考慮しない 考慮する

ConvolutionFilterで行われる計算

話を簡単にするため、7x7pxのグレースケールの画像を例に考えてみます。
以下の図は、7x7pxのグレースケールの画像を拡大したものです。
各ピクセルは0(黒)〜255(白)の強さを持っています。
File.png
図中に示したピクセルeに、以下のような3×3の行列を使ってConvolutionFilterを適用した場合、次のような計算が行われます。

a -.5 b 0 c 0
d 0 e .5 f .5
g 0 h 0 i 0

File-1.png

  1. ピクセルeを含む周囲9ピクセル(a~i)と行列の数値が1:1で対応し、各ピクセルの強さと対応する行列の数値をかけ算します。
    例えば、ピクセルaは白(255)で、行列のaの数値(-0.5)とかけ算します。
    a x -0.5 = 255 x -0.5 = -128
    同様に、bからiまでのピクセルを計算します。
    b x 0 = 255 x 0 = 0
    c x 0 = 255 x 0 = 0
    d x 0 = 255 x 0 = 0
    e x 0.5 = 255 x 0.5 = 128
    f x 0.5 = 255 x 0.5 = 128
    g x 0 = 255 x 0 = 0
    h x 0 = 255 x 0 = 0
    i x 0 = 255 x 0 = 0
  2. 9ピクセル全てについてかけ算が終わったら計算結果を足し合わせます。
    -128 + 0 + 0 + 0 + 128 + 128 + 0 + 0 + 0 = 128
    この足し合わせた数値128が、新しいピクセルの強さです。
    つまり、255(白)だったピクセルeが128(グレー)に変更されます。
    (この計算結果をさらに割ったりオフセットしたりすることもありますが、このエントリーでは詳しくは扱いません。)
  3. この操作を、画像の全てのピクセルに対してそれぞれ行います。
    図は、ConvolutionFilterの最終的な適用結果です。
    before
    before.png
    after
    after.png

理屈は簡単ですよね?
例では3×3の行列を使いましたが、4×4でも3×1でも行列であればどんな行列でも使えるようです。
行列の大きさが変わるとフィルタ適用時に考慮する周辺ピクセルの数が変わるというわけですね。
さて、理屈は非常にシンプルなConvolutionFIlterですが、行列から適用結果を想像する事がすごーく難しいです。
そこで、理解を深めるためにツールを作ってみました。
ツールの使い方を簡単に説明した後、このツールを使ってさらにConvolutionFilterを探求していきます。
つづく…

カテゴリー: ActionScript2, flash | 1件のコメント

アクティベーションオブジェクトによるメモリリーク

3回シリーズの最後です。
ようやく本題に入ります。

  1. AS3のクロージャ
  2. アクティベーションオブジェクトとスコープチェーン
  3. アクティベーションオブジェクトによるメモリリーク

エントリーの初回で、クロージャには、「メソッドクロージャ」と「関数クロージャ」があると書きましたが、
ちまたで(特にAS以外の言語で)クロージャと言えば、ASで言うところの関数クロージャで、メソッドクロージャと言うのはほぼAS独自の概念のようです。便利ですけどね。

今回はまず、関数クロージャの使いどころを例に挙げ、
その後に、関数クロージャの罠へと話を持っていきたいと思います。

関数クロージャによるローカル変数のカプセル化

よく関数クロージャの使用例として出てくるのが、関数クロージャを利用した「ローカル変数のカプセル化」です。
関数クロージャの実行前後に生成される、アクティベーションオブジェクトとスコープチューンを利用すると、
関数クロージャにローカル変数をカプセル化することができます。

例を挙げます。

package
{
    public class ClosureTest extends Sprite
    {
        public function ClosureTest()
        {
            //関数クロージャを取得
            var closure:Function = getClosure();
            //関数クロージャを実行
            trace(closure());
            trace(closure());
            trace(closure());    
        }
        private function getClosure():Function
        {
            var n:int = 0;
            //関数クロージャを生成して返す
            return  function() : int
                    {
                        return ++ n;
                    }
        }
    }
}

実行結果は

// output
// 1
// 2
// 3

メソッド’getClosure’のローカル変数’n’に注目します。
通常この変数’n’はローカル変数ですので、このメソッド内でしかアクセスできません。
また、メソッドを抜けると破棄されるはずです。
しかし、実行結果を見ると破棄されている様子はなくしっかりと出力されています。

何が起こっているかみてみます。
前回のエントリーと同じ表現で、コード中にスコープチェーンを書き入れてみると、以下のようになります。

package
{
    public class ClosureTest extends Sprite
    {
        public function ClosureTest()
        {
            //関数クロージャを取得
            var closure:Function = getClosure();
            //関数クロージャを実行
            trace(closure());
            trace(closure());
            trace(closure());    
        }
        private function getClosure():Function
        {
            /**
             * メソッド'getClosure'のスコープチェーン
             * [@ getClosure{n:0} - #ClosureTest]
             */
            var n:int = 0;
            //関数クロージャを生成して返す
            return  function() : int
                    {
                        /**
                          * 関数クロージャのスコープチェーン
                         * [@anonymous{} - @ getClosure{n:0} - #ClosureTest]
                         */
                        return ++ n;
                    }
        }
    }
}

関数クロージャは自身のスコープチェーンで、識別子’n’を解決します。
スコープチェーンの先頭は、自身のアクティベーションオブジェクトです。
この中にはプロパティnはありません。
なので、スコープチェーンの次のオブジェクトを探します。
次のオブジェクトは@ getClosure{n:0} で、メソッド’getClosure’のアクティベーションオブジェクトです。
このアクティベーションオブジェクトは、関数クロージャの実行時に関数クロージャのスコープチェーンに追加されたものです。(前回のエントリー参照)
このオブジェクトのプロパティにnが存在するので関数クロージャはこのnの値を使用します。

こうやって、呼び出しもとの親関数のローカル変数を、関数クロージャはまるで自分の持ち物のように扱えます。
しかもこの関数クロージャのスコープチェーンに保存された’n’は、もとの持ち主である親関数ですらアクセスする手段を持ちません。
なぜなら、メソッド’getClosure’のスコープチェーン はメソッドを抜けると破棄されるからです。

この、アクティベーションオブジェクトとスコープチェーンを利用したオブジェクトの保存方法を、いわゆる「クロージャによる変数のカプセル化」などと言います。

その他のクロージャの使いどころ

他には、「遅延評価」というものがあるようですが、今回は触れません。
興味のある方は以下を。ASでも実装できます。
参考:http://blog.livedoor.jp/dankogai/archives/50996734.html

イベントリスナに関数クロージャを利用する

イベントリスナを関数クロージャにすると、以下のようなことができます。

package
{
    public class ClosureTest2 extends Sprite
    {
        public function ClosureTest2()
        {
            initialize("DEFAULT");
        }
        private function initialize(execMode:String)
        {
            var m:String = execMode;
            stage.addEventListener( MouseEvent.CLICK, 
                                    function(e:MouseEvent)
                                    {
                                        trace(m);
                                    });
        }
        
    }
}

メソッド’initialze’に文字列”DEFAULT”を引数として渡しています。
(テストのためコンストラクタで呼び出していますが、実際は他のクラスから呼び出されると思って下さい。)
ステージがクリックされた時、この渡した文字列を出力したいとします。

通常このような非同期処理をする場合は、インスタンス変数として”DEFAULT”を保存すると思います。
しかし、この”DEFAULT”が、ステージをクリックされた時だけ使用される場合、
わざわざそのためにインスタンス変数を用意するのもためらわれます。
(この考えは間違っているのかもしれませんが、多すぎるインスタンス変数が僕は好きじゃありません。)
そこで、上記のように参照をカプセル化し、必要となる時までお手軽に保存することができます。

ちなみに、アクティベーションオブジェクトは、ローカル変数だけじゃなく「関数に渡された引数」も含まれるので、
実は、引数をローカル変数に一度保存する必要はありません。

package
{
    public class ClosureTest2 extends Sprite
    {
        public function ClosureTest2()
        {
            initialize("DEFAULT");
        }
        private function initialize(execMode:String)
        {
            stage.addEventListener( MouseEvent.CLICK, 
                                    function(e:MouseEvent)
                                    {
                                        /**
                                         * スコープチェーン
                                         * [@anonymous{} - @initialize{execMode:"DEFAULT"}]
                                         */
                                        trace(execMode);
                                    });
        }
        
    }
}

これでも動作します。
このテクニックは、結構応用しがいがあると思いませんか?

ただ、気をつけなければいけないことがあります。
今回のケースでは、関数クロージャはStageからリスナとして参照されます。
なので、関数クロージャと関数クロージャにカプセル化されているString”DEFAULT”を破棄する(ガベージコレクトさせる)ためには、
removeEventListener()する必要があります。
(addEventListener時に弱い参照を使う方法は、基本的に無効です。弱い参照だと、関数クロージャはどこからも参照されず、すぐにガベージコレクトされるからです。
それを回避するために、インスタンス変数に関数クロージャを保存するくらいなら、素直にメソッドクロージャを使った方がいいと思います。)

以下の例では、trace()を実行した後、自分自身(関数クロージャ)をStageのリスナーから削除します。
(arguments.calleは実行中の関数を返します)

package
{
    public class ClosureTest2 extends Sprite
    {
        public function ClosureTest2()
        {
            initialize("DEFAULT");
        }
        private function initialize(execMode:String)
        {
            stage.addEventListener( MouseEvent.CLICK, 
                                    function(e:MouseEvent)
                                    {
                                        trace(execMode);
                                        stage.removeEventListener(MouseEvent.CLICK, arguments.callee);
                                    });
        }
        
    }
}

このケースはごく簡単なサンプルなので、これで問題ないように見えますが、
事実上、イベントが発生したときにしかremoveListenerするチャンスがない以上、一定のリスクが存在します。
カプセル化するオブジェクトの破棄を、シビアにとらえなければいけない局面ではこの方法はお勧めしません。
何らかの理由でイベントのディスパッチが失敗すると、カプセル化したオブジェクトを破棄する手段(とアクセスする手段)がなくなるかもしれないからです。

アクティベーションオブジェクトによる意図しないメモリリーク

3回に分けて長々とエントリーを書きましたが、やっと佳境に入ってきました。
基本的な仕組みの説明が全て終わったので、実際にやってしまいがちなメモリリークするコード例を紹介します。

わりと複雑なアプリケーションを作っているとします。
MyApplicationは、クライアントクラスから初期化されるとします。

package
{
    public class MyApplication extends Sprite
    {
        public function MyApplication()
        {
        }
        
        public function initialize(mode:String) : void
        {
            /**
             * このアプリケーションは、ステージのクリックを検知し、開始する。
             */ 
            stage.addEventListener( MouseEvent.CLICK,
                                    function(e:Event)
                                     {
                                         start( mode );    
                                     });
        }        
        
        /**
         * アプリケーションをスタート
         */ 
        private function start(mode:String):void
        {
            trace("Application start", mode);
        }
    }
}

メソッド’initialize’が呼ばれ初期化処理された後、ステージをクリックするとアプリケーションが開始されます。
さっきのサンプルと同じように、関数クロージャに、initializeメソッドにわたされた引数をカプセル化しています。
ここまでは問題ありません。

ただ実際の実装はこんなにシンプルな物ではなく、
初期化にはそれなりの命令文が続くはずです。

package
{
    public class MyApplication extends Sprite
    {
        public function MyApplication()
        {
        }
        
        public function initialize(mode:String) : void
        {
            /**
             * めっちゃ重いオブジェクトを取得
             * -------------------------------------------
             * ローカル変数に格納することにより、
             * 一連の初期化処理が終わりこの関数を抜けた後、
             * このオブジェクトの参照は破棄されることを期待している。
             */ 
            var renderingData:Array = RenderSourceServise.getRenderingSource();
            
            /**
             * このアプリケーションは、ステージのクリックを検知し、開始する。
             */ 
            stage.addEventListener( MouseEvent.CLICK,
                                    function(e:Event)
                                     {
                                         start( mode );    
                                     });
        }        
        
        /**
         * アプリケーションをスタート
         */ 
        private function start(mode:String):void
        {
            trace("Application start", mode);
        }
    }
}

たとえば、ビューをレンダリングするために、かなり大きなビットマップ画像の配列を取得したとします。
察しのいい方ならお分かりでしょうが、この時点で結構深刻な問題が生まれています。

そうです。
巨大な配列、renderingDataが関数クロージャにカプセル化されてしまうのです。

関数クロージャ内で、renderingDataへの参照はありませんが、そんなことは関係ありません。
関数クロージャ内で使われようがそうでなかろうが、アクティベーションオブジェクトにしっかり保存されてしまうのです。
前回からのエントリーをみていただければ当然のことだとわかると思います。
そんなばかな!とにわかに信じがたいかもしれませんが、本当です。怖いですねー。
僕も最初にこれに気づいたときには愕然としました。ぶるぶる。

さらに、実際には、このinitializeメソッドには他にも多くのローカル変数が使われるはずです。
それらが「すべて!」、そう「すべて!」カプセル化されます。
もちろん、問題は、カプセル化されること自体ではありません。
リスナとして参照され続けるということが本当の問題です。
ローカル変数だから関数を抜けると破棄されると思っていたのに、こんな罠が!ですよね。
関数クロージャとイベントリスナの素敵なコラボレーションですね。

関数クロージャを他の人に渡す時は、常にこの問題に注意を払う必要があります。
他にどんなケースがあるでしょうか?
ありがちな例をいくつか挙げてみます。

package
{
    public class MyApplication extends Sprite
    {
        public function MyApplication()
        {
        }
        
        public function initialize(mode:String) : void
        {
            /**
             * アプリケーションマネージャを生成
             * アプリケーションマネージャを初期化し、
             * 非同期の初期化処理が終わったらアプリケーションを開始する。
             */ 
            _myManager = new Manager();
            _myManager.addEventListenert( Event.Complete,
                                        function(e:Event)
                                         {
                                             start( mode );    
                                         });
            
            /**
             * めっちゃ重いオブジェクトを取得
             * -------------------------------------------
             * ローカル変数に格納することにより、
             * 一連の初期化処理が終わりこの関数を抜けた後、
             * このオブジェクトの参照は破棄されることを期待している。
             */ 
            var renderingData:Array = RenderSourceServise.getRenderingSource();
        }
        
        
        /**
         * アプリケーションをスタート
         */ 
        private function start(mode:String):void
        {
            trace("Application start", mode);
        }
    }
}

これも、リスナ&関数クロージャです。

こんな風に上下逆になるだけで、普通にスルーしてしまいそうです。
removeEventListener()するまではManagerインスタンスから参照され続けます。。
関数クロージャとカプセル化された巨大なオブジェクトが。。
頼んでもないのにです。

気をつけないといけないのは、イベントリスナだけではないです。

package
{
    public class MyApplication extends Sprite
    {
        public function MyApplication()
        {
        }
        
        public function initialize(mode:String) : void
        {
            /**
             * めっちゃ重いオブジェクトを取得
             * -------------------------------------------
             * ローカル変数に格納することにより、
             * 一連の初期化処理が終わりこの関数を抜けた後、
             * このオブジェクトの参照は破棄されることを期待している。
             */ 
            var renderingData:Array = RenderSourceServise.getRenderingSource();
            
             /**
             * 何らかの理由で、1秒後にアプリケーションを開始したい。
             */ 
            setTimeout( function()
                        {
                            start( mode );
                        }, 1000);
        }
        
        
        /**
         * アプリケーションをスタート
         */ 
        private function start(mode:String):void
        {
            trace("Application start", mode);
        }
    }
}

こんなのも、よくやってしまいます。
かならずclearInterval()しましょう。
clearInterval()するまではグローバルから参照され続けます。。
関数クロージャとカプセル化された巨大なオブジェクトが。。
頼んでもないのにです。

ほかには、コールバック関数として関数クロージャを他のオブジェクトに渡したりとかでしょうか。
AS2スタイルでイベントハンドラが書けるラッパーとか使っている人(そんなラッパーがあるかどうか知らないが)は注意しましょう。

btn.onRelease = function(){};

みたいなやつ。
これも立派な関数クロージャ。
dynamicクラス大好きな人はばんばんやってそうな予感もします。

まとめ

じゃぁ、どうすればいいの?って話になると思うのですが、
メモリリークのリスクが発生するのは、関数クロージャを他のオブジェクトに渡すときです。
なので、ブラックボックスなオブジェクトには渡さない方がいいんじゃないでしょうか。
すくなくとも、EventDispatcherやsetTimeoutなど、参照を断ち切るインターフェイスを持ったクラスか、
仕事が終わったら参照を破棄することを保証してくれるオブジェクトにだけ渡しましょう。

また、関数クロージャは使うべきときに使うようにしましょう。
使うべき時とはローカル変数のカプセル化や遅延評価を利用したいときです。
「メソッドを定義するのがめんどくさくて匿名関数ベタ書きしちゃいましたー。てへ」というのはよくないです。
自戒を込めて。
さらに、カプセル化のコード例の様に、関数クロージャを生成する際は生成する専用のメソッドを用意してあげると、リスクを最小限に出来ると思います。

ながなが読ませやがって、そんな落ちかよ!知ってたよ!ってひとはごめんなさい。
だって自分にとっては衝撃だったんだもん。

最後に、
「FlexBuilder3のプロファイラはパンドラの箱かもしれん」

カテゴリー: ActionScript3 | 3件のコメント

アクティベーションオブジェクトとスコープチェーン

3回シリーズの2回目です

  1. AS3のクロージャ
  2. アクティベーションオブジェクトとスコープチェーン
  3. アクティベーションオブジェクトによるメモリリーク

参照の解決

以下のようなコードがあるとします。

package
{
    public class MyClass extends Sprite
    {
         private function methodA():void
        {
        }
        
        private function methodB():void
        {
                    var f:Function = function()
                                    {
                                        trace(methodA);
                                    }
                    f();
        }
    }
}

methodBを実行すると、メソッド’methodA’が出力されます。

// output
// function Function() {}

ごく普通で当たり前の光景ですよね。
でも、よく考えると不思議です。
関数クロージャ(匿名関数)’f’はFunctionインスタンスで、MyClassインスタンスとは独立した別のオブジェクトです。
なぜ、関数クロージャ’f’はMyClassのprivateなメソッドにアクセスできるのでしょうか?

僕は、「methodAの前にthisが省略されていて、そのthisはMyClassインスタンスを指しているから」だと勝手に理解していたのですが、調べてみると全然違いました。

前回のエントリーで書いたように、
関数クロージャ’f’の中でtrace(this)すると、[object global]が出力されます。
つまり、仮にthisが省略されていたとしても、関数クロージャ’f’内でのthisはMyClassインスタンスを指しません。

実は、このmethodAはスコープチェーンによって解決されています。

スコープチェーン

僕は、つい頭の中で「スコープチューン」と読んでしまうのですが、「チェーン」です。鎖です。
参照を解決するオブジェクトが連なった鎖をイメージするとよいかと思います。

関数が実行されるたびにスコープチェーンが生成されます。

  1. まず、methodBが実行されるとき、MyClassインスタンスが持つ全てのプロパティが参照できるオブジェクトが生成され、methodBのスコープチェーンに格納されます。(多分。。)
  2. つぎに、methodBのなかで関数クロージャ’f’が実行されるときに、そのスコープチェーンが渡されます。(多分。。)
  3. そして、tarce(methodA)でmethodAを解決する際に、このスコープチェーンを検索します。
  4. 最後に、スコープチェーンにある、1で出来たオブジェクトが検索にヒットし、めでたくmethodAにアクセスすることができます。

    (この1〜4の行程は、十分な裏が取れず推測が混じっているので正しくないかもしれない

スコープチェーンが生成されるときに作られるオブジェクトについては、リファレンスによると

Second, a scope chain is created that contains an ordered list of objects that Flash Player checks for identifier declarations.
次に、”スコープチェーン” が作成され、そこには Flash Player によって識別子宣言の有無がチェックされるオブジェクトが列挙されたリストが含まれます。

とありますが、正直この説明は漠然としすぎていてよくわかりません。
が、AS3のスコープチェーンについてのこれ以上詳しいリソースを見つけられませんでした。

知りたいのは、その「 Flash Player によって識別子宣言の有無がチェックされるオブジェクト」がどういうルールで生成されるのかなんですが。。
詳細をご存知の方はコメントください!

アクティベーションオブジェクト

さて、さっきの説明でははしょったんですが、
関数の実行時には、毎回アクティベーションオブジェクトと呼ばれるオブジェクトが生成されて、関数のスコープチェーンの先頭に加えられます。

Any time a function begins execution, a number of objects and properties are created. First, a special object called an activation object is created that stores the parameters and any local variables or functions declared in the function body. You cannot access the activation object directly, because it is an internal mechanism.
関数が実行を開始するたびに、多数のオブジェクトおよびプロパティが作成されます。最初に、”アクティベーションオブジェクト” という特別なオブジェクトが作成され、そこには関数本体で宣言されるパラメータとローカル変数または関数が格納されます。アクティベーションオブジェクトは内部メカニズムであるため、そこに直接アクセスすることはできません。
(ActionScript 3.0 のプログラミング > ActionScript 言語とシンタックス > 関数 > 関数のスコープ)

また、関数クロージャが実行される場合は、関数クロージャを呼び出した親関数のアクティベーションオブジェクトが関数クロージャのアクティベーションオブジェクトの後に追加されます。メソッドクロージャの実行時にはこのアクティベーションオブジェクトの受け渡しは行われません。

For a nested function, the scope chain starts with its own activation object, followed by its parent function’s activation object.
ネストされた関数の場合は、スコープチェーンはそのアクティベーションオブジェクトから始まり、その後に親関数のアクティベーションオブジェクトが続きます。

ちょっと例を変えて、説明を試みてみます。

public class ScopeTest extends Sprite
{
    private var myNum:int;
    public function ScopeTest()
    {
        methodA();
    }
    private function methodA():void
    {
        var myNum:int = 2;
        methodB();
    }
    private function methodB():void
    {
        var myNum:int = 3;
        var f:Function = function():void
                    {
                        var myNum:int = 4;
                        trace(myNum);
                    };
        f();
    }
}

このコードではmyNumという識別子を持つ変数が4カ所で定義されています。

  • ScopeTestクラスのプロパティ
  • methodAのローカル変数
  • methodBのローカル変数
  • 関数クロージャ’f”のローカル変数

関数クロージャ内でこのmyNumをtraceしたときに出力される値は何でしょうか?

// output
// 4

では、関数クロージャのローカル変数であるmyNumをコメントアウトしてみます。

public class ScopeTest extends Sprite
{
    private var myNum:int;
    public function ScopeTest()
    {
        methodA();
    }
    private function methodA():void
    {
        var myNum:int = 2;
        methodB();
    }
    private function methodB():void
    {
        var myNum:int = 3;
        var f:Function = function():void
                    {
                        //var myNum:int = 4;
                        trace(myNum);
                    };
        f();
    }
}

こんどは出力結果は3になり、methodBのmyNumを参照するようになりました。

//output
//3

さらに、methodBのローカル変数であるmyNumをコメントアウトしてみます。

public class ScopeTest extends Sprite
{
    private var myNum:int;
    public function ScopeTest()
    {
        methodA();
    }
    private function methodA():void
    {
        var myNum:int = 2;
        methodB();
    }
    private function methodB():void
    {
        //var myNum:int = 3;
        var f:Function = function():void
                    {
                        //var myNum:int = 4;
                        trace(myNum);
                    };
        f();
    }
}

結果は以下の通りで、ScopeTestインスタンスのmyNumプロパティが参照されます。

// output
// 0

この結果から、methodAのローカル変数myNumにはアクセスしていないことがわかります。
関数クロージャ’f’はmethodAが見えていません。
つまり、「methodAのローカル変数myNumは、関数クロージャ’f’のスコープチェーンのなかには存在しない」ということができます。

スコープチェーンが作られていく様子を詳細に追いかけてみます。
注意するべきポイントは、「関数クロージャが実行されるときに行われるアクティベーションオブジェクトの受け渡し」です。(下のリストの中の9の後半です)

  1. methodAが実行されるときにアクティベーションオブジェクトが生成されます。
    このアクティベーションオブジェクトには、プロパティ’myNum’を持ち、methodAのローカル変数’myNum’の値が格納されます。
    便宜上、アクティベーションオブジェクトを@methodA{myNum:2}と表現することにします。

  2. 次にmethodAのスコープチェーンが生成されます。
    このスコープチェーンにはScopeTestインスタンスのprivateな変数であるmyNumの参照をもつオブジェクトが含まれます。
    便宜上、このオブジェクトを#ScopeTest{myNum:0}と表現することにします。

  3. 2で作成されたスコープチェーンの先頭にアクティベーションオブジェクトが追加されます。
    methodAのスコープチェーンは以下のような順番になっています。
    [@methodA{myNum:2} – #ScopeTest{myNum:0}]
    便宜上、スコープチェーンをこのように表現します。

  4. methodBが呼び出されます。
  5. methodBが実行されるときにアクティベーションオブジェクト @methodB{myNum:3, f:関数クロージャ} が生成されます。
    このアクティベーションオブジェクトはmethodBのローカル変数をプロパティとして持ちます。

  6. methodBのスコープチェーンが生成され、アクティベーションオブジェクトが先頭に追加されます。
    methodBのスコープチェーンは以下のような順番になっています。
    [@methodB{myNum:3, f:関数クロージャ} – #ScopeTest{myNum:0}]

  7. 関数クロージャ’f’が呼び出されます。
  8. 関数クロージャ’f’が実行されるときにアクティベーションオブジェクト @f{myNum:4} が生成されます。
    このアクティベーションオブジェクトは関数クロージャ’f’のローカル変数をプロパティとして持ちます。

  9. 関数クロージャ’f’のスコープチェーンが生成され、アクティベーションオブジェクトが先頭に追加されます。
    さらに、このとき、呼び出しもとの親関数のアクティベーションオブジェクト(5で作られた @methodB{myNum:3, f:関数クロージャ} ) がその後に追加されます。
    [@f{myNum:4} – @methodB{myNum:3, f:関数クロージャ} – #ScopeTest{myNum:0}]

[@f{myNum:4} – @methodB{myNum:3, f:関数クロージャ} – #ScopeTest{myNum:0}]
最終的に、これが、関数クロージャ’f’のスコープチェーンになります。

左が先頭のオブジェクトで、右にオブジェクトが続きます。
関数が識別子を解決する場合は、先頭のオブジェクトから探していきます。

今回の例では、myNumの検索は先頭のアクティベーションオブジェクトでヒットし、関数クロージャ’f’のmyNumが参照されます。myNumを1つづつコメントアウトすると、検索にヒットするオブジェクトがひとつずつ右に移動します。
これは先ほどの出力結果と一致し、methodAがスコープチェーンに含まれていないことも確認できます。


参考サイト:http://www2u.biglobe.ne.jp/~oz-07ams/prog/js-notes/scope.html


次回のエントリーに続く

カテゴリー: ActionScript3 | 1件のコメント

AS3のクロージャ

どうしても解消しないメモリリークを追いかけていったら、アクティベーションオブジェクトにたどりついたので、
自分の理解を深めるためにまとめてみます。
長くなりそうなので、3回のエントリーに分けます。

  1. AS3のクロージャ
  2. アクティベーションオブジェクトとスコープチェーン
  3. アクティベーションオブジェクトによるメモリリーク

関数

リファレンスによると、AS3の関数は「メソッド」と「関数クロージャ」の 2種類に分類されるようです。

There are two types of functions in ActionScript 3.0: methods and function closures.
ActionScript 3.0 の関数には「メソッド」と「関数クロージャ」の 2 種類があります。
(ActionScript 3.0 のプログラミング / ActionScript 言語とシンタックス / 関数)

関数クロージャとは何かというと、メソッド以外の全ての関数のことです。

Whether a function is a called a method or a function closure depends on the context in which the function is defined. A function is called a method if you define it as part of a class definition or attach it to an instance of an object. A function is called a function closure if it is defined in any other way.
関数をメソッドと呼ぶか関数クロージャと呼ぶかは、関数が定義されたコンテキストによって決まります。関数をクラス定義の一部として定義した場合、またはオブジェクトのインスタンスに関連付けた場合は、メソッドと呼びます。関数がその他の方法で定義された場合は、関数クロージャと呼びます。

さらに、メソッドは特にメソッドクロージャ(バインドメソッド)と呼ばれる使われ方をすることがあります。

A bound method, sometimes called a method closure, is simply a method that is extracted from its instance. Examples of bound methods include methods that are passed as arguments to a function or returned as values from a function.
バインドメソッドは、単にインスタンスから抽出されるメソッドで、「メソッドクロージャ」とも呼ばれます。バインドメソッドの例には、関数にパラメータとして渡され、関数から値として返されるメソッドがあります。
(ActionScript 3.0 のプログラミング / ActionScript のオブジェクト指向プログラミング / クラス)

クロージャ

つまり、AS3のクロージャには「メソッドクロージャ」と「関数クロージャ」があるということですね。
関数の分類を図にするとこんな感じです。
function.jpg


もうちょっと詳しくみていきます。
例えば以下のようなコードがあったとき、

package
{
    import flash.display.Sprite;
    public class FunctionTest extends Sprite
    {
        public function FunctionTest()
        {
        }
        
        
        private function onClick(e:Event=null):void
        {
            trace(this);
        }
        
        
        private function aboutMethodClosure():void
        {
            stage.addEventListener(MouseEvent.CLICK, onClick);
            
            var o:Object = {};
            o.onClick = onClick;
            o.onClick();
        }
        
        
        private function aboutFunctionClosure():void
        {
            var f:Function = function():void
                             {
                                trace(this);
                                trace(this is Function);
                             };
            f();
            var o:Object = {};
            o.f = f;
            o.f();
        }
    }
}

各関数は以下のように分類できます。

コンストラクタ

FunctionTestは通常コンストラクタといいますが、正確にはコンストラクタメソッドといってメソッドの一部です。

メソッドクロージャ

コード中のメソッド’aboutMethodClosure’の中で、
Stageのリスナとして’onClick’メソッドを渡しています。
このとき、onClickはクラスから抽出されています。
これがメソッドクロージャ。

また、その後にObjectインスタンスを生成して
そのインスタンスにonClickというメンバを定義し、onClickメソッドを代入しています。
ここで代入したonClickメソッドもクラスから抽出されたメソッドなのでメソッドクロージャ。

関数クロージャ

関数クロージャは、メソッド以外の全ての関数ですが、
ほとんどの場合メソッド内で新しく定義された関数が関数クロージャになります。
関数内で新しく定義された関数であることから「ネストされた関数」という言い方もするようです。

この例では匿名関数を生成し、変数’f’に代入していますが、以下のような書き方も出来ます。

package
{
    class myClass
    {
        private function myFuncA() : void
        {
            function myFuncB() : void
            {
            }
        }
    }
}

この場合、
myFuncAはメソッド、
myFuncBは関数クロージャ。

メソッドクロージャと関数クロージャの違い

メソッドクロージャ’onClick’と、関数クロージャ’f’は
実行されるとどちらもtrace(this)します。
メソッドクロージャと関数クロージャでは、このthisが指す参照が大きく違います。

実行結果は以下のようになります。

//メソッドクロージャ
[object FunctionTest]
[object FunctionTest]
//関数クロージャ
[object global]
[object Object]

メソッドクロージャの方は、どのような呼び出し方をされても
thisの参照先は「そのメソッドが定義されたインスタンス」を、
つまりFunctionTestインスタンスを指します。
このようにthisの参照先が常に束縛されているので「バインドメソッド」とも呼ぶようです。

それに対して、関数クロージャは
呼び出し方によって参照先が変わります。
最初のf()での呼び出しですが、このfはどのオブジェクトの持ち物でもありません。
が、このときアクティベーションオブジェクトと呼ばれるObjectが内部的に生成されていて、
fはこのアクティベーションオブジェクトのスコープで実行されます。

thisの参照を解決するためにスコープチェーンをたどりこのアクティベーションオブジェクトに到達し、
thisの参照先がアクティベーションオブジェクトになっています。
(ここの理解は自信なし。スコープチェーンをたどってGlobalに達しているのかもしれん)

(thisの値はスコープチェーンと関係なかった。たんにこの関数が誰に関連づけられているかでthisが指すものは決まる。誰の持ち物でもない場合はglobalオブジェクトが返る)
それに対して、次のo.f()の呼び出しでは、このfはoに含まれた関数なので
thisはObjectインスタンス’o’を指しています。

AS2の頃のクロージャ

AS2では「メソッドクロージャ」はなく、全て「関数クロージャ」でした。
なので、以下のようにイベントのリスナに登録すると
クロージャはMouseのスコープで実行されてしまい、thisはMouseを指していました。

class MyClass
{
    function MyClass()
    {
        Mouse.addListener(onClick);
    }
    private function onClick():void
    {
        trace(this);
    }
}

これを回避するために、Delegate.create()していたのはいい思い出ですよね。
AS3でのメソッドクロージャの導入は、この面倒を解決するためのものだと勝手に想像しています。


次回のエントリーへつづく

カテゴリー: ActionScript3 | 2件のコメント

ASのガベージコレクトは参照カウント方式じゃなかった

かなり今更ですが、
「そのオブジェクト、本当に消えていますか?」で、ガベージコレクトは参照カウント方式で行われると書きましたが、
どうやらマーク・アンド・スウィープ方式と呼ばれる方法で行われているようです。
参考:akihiro kamijo: Flash Player 9 のガーベジコレクション


参照カウント方式

参照カウント方式の場合、循環参照しているオブジェクトは永久に破棄されません。
gc_a0.jpg
矢印は参照方向、数字は参照カウントを表しています。
最下段の二つのインスタンスは、お互いに参照し合っています。(循環参照)
gc_a1.jpg
ここで、図のように参照を切ったとしても、、
gc_a2.jpg
依然として、最下段のインスタンスの参照カウントは0にはならず、
いつまでたってもガベージコレクトの対象にはなりません。

マーク・アンド・スウィープ方式

マーク・アンド・スウィープ方式では、循環参照の問題は起きません。
いままで僕はオブジェクト同士が循環参照にならないように気を使っていましたが、
徒労だったというわけです。
マークアンドスウィープ方式は以下のような感じです。
gc_b0.jpg
ルート(おそらく、StageやGlobal)から参照をたどれるオブジェクトにマークをつけていきます。
参照をたどれるオブジェクトがなくなった時点で、まだマークされていないオブジェクトがあったら、
ガベージコレクトの対象となります。
たとえば、以下のように参照を切った場合、
gc_b1.jpg
最下段の二つのインスタンスはルートから参照をたどれなくなるので、
ガベージコレクトの対象となり破棄されます。
gc_b2.jpg

FlashPlayerが参照カウント方式でガベージコレクトして「いない」ことを確かめるサンプルです。
循環参照しているインスタンスを意図的に生成しています。
FlashPlayerが参照カウント方式でガベージコレクトしていれば、
メモリ使用量が増え続けるはずですが、
実行するとある程度増えたところでお掃除される様子が確認できます。
(Flashのガベージコレクトはそんなに頻繁には行われないようです。
僕の環境だと600Mbほどメモリを使用したところで、ガベージコレクトされました。
ガベージコレクトされる頻度は、おそらく実行環境に依存すると思います。)

package  
{
    import flash.system.System;    
    import flash.events.Event;    
    import flash.display.BitmapData;    
    import flash.text.TextField;    
    import flash.display.Sprite;
    import flash.utils.setInterval;

    public class Main extends Sprite 
    {
        private var _memoryViewer : TextField;
        public function Main ()
        {
            //相互参照なオブジェクトの生成
            setInterval(createEachReferencedObj, 1000);
            
            //メモリ表示用テキストエリア
            _memoryViewer = new TextField();
            addChild(_memoryViewer);
            addEventListener(Event.ENTER_FRAME, checkMemory);
        }

        
        
        /**
         * 相互参照なオブジェクトを生成
         */
        private function createEachReferencedObj () : void
        {
            var objA : Object = {bmp:new BitmapData(2000, 2000, true)};
            var objB : Object = {ref:objA};
            objA.ref = objB;
        }

        
        /**
         * メモリ使用量を表示
         */
        private function checkMemory (event : Event) : void
        {
            var bytes : int = System.totalMemory;
            _memoryViewer.text = String(bytes >> 20) + " Mb";
        }
    }
}
カテゴリー: ActionScript3 | コメントする

AS3におけるroot参照

AS2はもとより、AS3でもrootを参照することはあまりないと思うけど、AS3でのルート参照を調べてみたら、かなり直感的ではないことがわかったのでレポートします。
いざというときに役に立つかも。

●そもそもFlashにおけるルートとはなんぞや

画面上に表示されるオブジェクトを「表示オブジェクト」とし、表示オブジェクトのコンポジット構造を「表示オブジェクトツリー」とすると、「ルート」とは「表示オブジェクトツリー」のルートノードである
という定義でいいと思う。多分。

問題は、AS2ではすべての表示オブジェクトは、一本の「表示オブジェクトツリー」で表現されていたが、(例外として、loadMovieNum()を使用した場合は、そこから新しく表示オブジェクトツリーが生成されていた。もうあんまり覚えてないけど。あと、MovieClip._lockrootとか。。使ってなかったけど。)AS3の場合は、どうもそう単純な話ではない様子。

●AS3におけるroot参照

たとえば、以下のような表示オブジェクトツリーがあるとする。(クリックで拡大します)
a image of display object tree
Stage直下にLoaderやDisplayObjectが存在するのに違和感を感じるかもしれないが、
Stage.addChild()が可能である以上、これは普通に起こりえる状況。
さて、それぞれのオブジェクトのrootプロパティはいったい何を指すでしょうか。

答えは ↓
一見するとかなり複雑に見える。(クリックで拡大します)
a image of pointing root

で、がんばって、一般化してみた。
あるオブジェクトのrootプロパティが何を指すかは、そのオブジェクトの親をたどっていき、

1・Loaderにぶちあたったら、そのLoaderに読み込まれたDisplayObjectがroot。
2・DocumentClassにぶちあたったら、それがroot。
3・Stageにぶちあたったら、それがroot。

言い方を変えると、
Stageから下におりていき、DocumentClassかLoaderに読み込まれたDisplayObjectにぶちあたったら
そのオブジェクトをrootとしてあたらしい表示オブジェクトツリーが開始される。

ちなみに、表示オブジェクトツリーを色分けしてみるとこんな感じ。
a image of pointing to root

最後に検証したコードと出力結果です。

package
{
    import flash.display.*;
    import flash.net.*;
    import flash.events.*;
    public class Main extends Sprite
    {
        private static  var cnt : int = 0;
        
        public function Main ()
        {
            if (stage)
            {
                //Test1
                test(this);
            }
        }

        
        
        
        public override function toString () : String
        {
            return (parent is Loader) ? "Loader.content" : "DocumentClass";
        }

        
        
        
        private function test (target : DisplayObjectContainer) : void
        {
            cnt ++;
            trace("¥n¥n/************************************");
            trace(" *" + cnt + "¥tTest about " + target + "'s root");
            trace(" ****************************/");
            var stageChildren : int = stage.numChildren;
            trace(" * now, Stage has " + stageChildren + "children");
            for (var i : int;i < stageChildren; i ++) trace("¥t¥t" + stage.getChildAt(i));
            
            //check parent
            trace(target + "'s parent is " + target.parent);
            
            //check root
            trace(target + "'s root is " + target.root);
            
            //check Stage's root
            trace("Stage's root is always Stage : " + (target.stage.root is Stage));
            
            if (cnt == 1)
            {
                //check DocumentClass'children
                var spr : Sprite = new Sprite();
                var spr2 : Sprite = new Sprite();
                spr.addChild(spr2);
                target.addChild(spr);
                trace("root of DocumentClass's child  is DocumentClass : " + (spr.root == target && spr2.root == target));
                
                var anortherChildOfStage : Sprite = new Sprite();
                var grandChildOfStage : Sprite = new Sprite();
                anortherChildOfStage.addChild(grandChildOfStage);
                target.stage.addChild(anortherChildOfStage);
                //Test2
                test(anortherChildOfStage);
            } else if (cnt == 2)
            {
                //Test3
                test(target.getChildAt(0) as DisplayObjectContainer);
            } else if (cnt == 3)
            {
                var loader : Loader = new Loader();
                loader.load(new URLRequest("root.swf"));
                loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadedLoader);
            }
            else if(cnt == 5)
            {
                var loader2 : Loader = new Loader();
                loader2.load(new URLRequest("root.swf"));
                loader2.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadedLoader2);
            }
        }

        
        
        
        private function onLoadedLoader (event : Event) : void
        {
            var loader : Loader = LoaderInfo(event.target).loader;
            stage.addChild(loader);
            //Test4
            test(loader);
            //Test5
            test(loader.content as DisplayObjectContainer);
        }

        
        
        
        private function onLoadedLoader2 (event : Event) : void
        {
            var loader : Loader = LoaderInfo(event.target).loader;
            this.addChild(loader);
            //Test6
            test(loader);
            //Test7
            test(loader.content as DisplayObjectContainer);
        }
    }
}

//結果

/************************************
*1	Test about DocumentClass's root
****************************/
* now, Stage has 1children
DocumentClass
DocumentClass's parent is [object Stage]
DocumentClass's root is DocumentClass
Stage's root is always Stage : true
root of DocumentClass's child  is DocumentClass : true
/************************************
*2	Test about [object Sprite]'s root
****************************/
* now, Stage has 2children
DocumentClass
[object Sprite]
[object Sprite]'s parent is [object Stage]
[object Sprite]'s root is [object Stage]
Stage's root is always Stage : true
/************************************
*3	Test about [object Sprite]'s root
****************************/
* now, Stage has 2children
DocumentClass
[object Sprite]
[object Sprite]'s parent is [object Sprite]
[object Sprite]'s root is [object Stage]
Stage's root is always Stage : true
/************************************
*4	Test about [object Loader]'s root
****************************/
* now, Stage has 3children
DocumentClass
[object Sprite]
[object Loader]
[object Loader]'s parent is [object Stage]
[object Loader]'s root is [object Stage]
Stage's root is always Stage : true
/************************************
*5	Test about Loader.content's root
****************************/
* now, Stage has 3children
DocumentClass
[object Sprite]
[object Loader]
Loader.content's parent is [object Loader]
Loader.content's root is Loader.content
Stage's root is always Stage : true
/************************************
*6	Test about [object Loader]'s root
****************************/
* now, Stage has 3children
DocumentClass
[object Sprite]
[object Loader]
[object Loader]'s parent is DocumentClass
[object Loader]'s root is DocumentClass
Stage's root is always Stage : true
/************************************
*7	Test about Loader.content's root
****************************/
* now, Stage has 3children
DocumentClass
[object Sprite]
[object Loader]
Loader.content's parent is [object Loader]
Loader.content's root is Loader.content
Stage's root is always Stage : true

出力の1〜7の番号は、以下のオブジェクトに対応します。

●最後に

検証コード自体複雑になってしまって、もしかしたらまちがってるかもしれん。
あと、すべてのDisplayObjectについて検証したわけじゃないので、
Loaderみたいな特別なやつが他にもいるかもしれん。

ひとついえるのは、AS2の感覚でrootを使うと間違いなくはまる。
自分が何をしようとしているか、はっきりと確信を持っている時じゃなければ、
root参照は使わない方がいいんじゃないでしょうか。

カテゴリー: ActionScript3, AS3fromAS2 | 2件のコメント

privateが自分が思っていたほどprivateじゃなかった件について

今更、自分が凄い勘違いをしていたことに気づいてしまった。

privateキーワードは、可視性を当事者であるインスタンスのみに制限するものだと思ってた。
違うんだね。
privateが定義されているクラスのインスタンスからなら誰からでもアクセスできるんだね。
こんな凄い勘違い、今までよく問題にならなかったなと。。
思い込みが強すぎて、アクセスを試そうとすらしたことがなかった。
自分への戒めをこめてカミングアウトします。

package {
    public class MyClass {
        private var _privateValue : int;
        public function MyClass(v:int) {
            _privateValue = v;
        }

        public function printPrivateValue(c:MyClass) {
            /**
             * よそのインスタンスのprivateな変数にはアクセスできないと思ってた。
             *でもクラスが同じならアクセスできるんだね。
            */
            return c._privateValue;
        }
    }
}
//Client code
var c1 : MyClass = new MyClass(1);
var c2 : MyClass = new MyClass(2);
trace(c1.printPrivateValue(c2));
//output
2
カテゴリー: ActionScript3 | コメントする

flash.utils.Proxyの使いどころがわからん

flash.utils.Proxyって使えなくないですか?
あるオブジェクト、例えばSpriteのラッパークラスがあるとして、
このクラスを外から見るとSpriteとしてみせたい場合、
Spriteを継承するわけだけど、
ASは多重継承が出来ないので、いつも継承が使えるわけじゃない。
まぁ、「継承よりコンポジット」というOOPの指針もあることだし、
Spriteをコンポジットしてラップするとすると、
今度は、Spriteが持っている全てのプロパティやメソッドを
コンポジットしているSpriteに委譲するコードを書くのが超めんどくさい。
そこでProxyが使えるのかなーって思ったんだけど、
たとえばSpriteのように振る舞うProxySpriteは必ずProxyを継承しなければいけない仕様。。
これ外からみてもSpriteに見えないよ。
もちろん、ProxySpriteをSpriteに強制型変換も出来ない。
実に使えない。というか使いどころが想像できない。
ヘルプにあるサンプルは、Arrayの代理オブジェクトを例としてあげてるけど、
このサンプルからはProxy使っている意味が全く読み取れない。
Proxyをうまく使っている例ってみたことあります?

カテゴリー: ActionScript3 | コメントする