ホーム >> 左脳Script >> Adobe Flash >> Action Script >> クラス定義と静的初期化処理のタイミング

クラス定義と静的初期化処理のタイミング


通常クラスの静的初期化関数から、internal class を呼べない理由が知りたくて、クラス定義のタイミングを調査&検証していくうちに、色々と判ってしまいました。

興味深かったのが「クラス定義」処理のタイミングでした。自分の思惑からかけ離れた動作をしていたので、検証と結果、思いつく対処策をまとめてみました。



クラス定義のタイミング

こんな試験クラスを作ってみました。

classTest/test.as

package classTest 
{
    trace("test定義領域外");
    public class test
    {
        trace("test定義領域?");
        //  静的関数
        private static function staticFunc():void 
        {
            trace("test静的関数");
        }
        //  静的関数実行
        staticFunc();
        //
        public function test() 
        {
            trace("testコンストラクタ");
        }
    }
}
trace("testパッケージ指定領域外");

メインでは、このように記述し、トレースを観察してみました。

trace("testインスタンス作成");
var t:test = new test();


結果:
testインスタンス作成
test定義領域?
test静的関数
test定義領域外
testパッケージ指定領域外
testコンストラクタ


うむうむ、予想通り・・・アレ?!

「testコンストラクタ」の直前ではなく、testクラスの定義の前に「testインスタンス作成」が有るようです。

"クラスの定義"処理そのものは、初めてインスタンスが作成される直前に行われると初めて知りました。

そしてもう一つ、「test静的関数」の実行が、このタイミング(初めてインスタンスが作成される直前)で成されるとは思っていなかったので、今後のクラスのデザイン思考?を改める必要がありそうです。

とりあえず、もう少し「クラス定義」がされるタイミングについて観察してみます。


メンバ定義

インスタンス作成の記述を削除して、以下のクラスメンバ宣言をしてみました。

trace("testメンバ変数定義");
private var t:test;
すると、結果は以下のようになりました。
testメンバ変数定義
宣言するだけでは、クラス定義は実行されないようです。これが static変数であっても状況は同じでした。


constメンバ定義

const を付けて観察してみます。その為、必然的にnew(インスタンス作成)記述になります。

trace("test不変メンバ変数定義");
private const   t:test = new test();
結果、
test不変メンバ変数定義
test定義領域?
test静的関数
test定義領域外
testパッケージ指定領域外
testコンストラクタ
となりました。new でクラス定義が実行されていると言えるでしょう。


static constメンバ定義

同じ結果になるかと思っていた「static const での定義」が、微妙に異なった結果となりました。

以下、試験記述。

trace("test静的不変メンバ変数定義");
private static const    t:test = new test();
結果、
test定義領域?
test静的関数
test定義領域外
testパッケージ指定領域外
testコンストラクタ
test静的不変メンバ変数定義
宣言の前にあるトレース命令より、さらに前にクラス定義の実行がされているようです。


internal Class

ちょっとした構造体っぽいもの、記述を分離&纏めるマクロ的な使い方が出来る便利なものですが、これらの場合は何時実行されるのでしょうか?

classTest/test.as

package classTest 
{
    trace("test定義領域外");
    public class test
    {
        trace("test定義領域");
        //
        public function test() 
        {
            trace("testコンストラクタ");
        }
    }
}
trace("testパッケージ指定領域外");
internal class foo
{
    trace("foo定義領域");
    //
    public function foo() 
    {
        trace("fooコンストラクタ");
    }
}
trace("foo定義記述後");
メインでは、インスタンスを作成。

trace("testクラスのインスタンス作成");
var t:test = new test();

トレース結果は以下。
testクラスのインスタンス作成
test定義領域
foo定義領域
test定義領域外
testパッケージ指定領域外
foo定義記述後
testコンストラクタ

ちょっぴり予想の斜め上を行っていたようです。
領域外にあたる箇所、普通はコードを書かない箇所の順序が、記述順ではなく、
「クラスA定義領域」「クラスB定義領域」...「クラスA定義領域外」「クラスB定義領域外」...
のような順序で実行されています。


注意すべき記述と対処策

クラス定義の実行がこのような順序で行われている為、特に以下ような記述が大好きな人は注意が必要になるでしょう。

ex ) ありがちな初期化方法。

public class hongege
{
    //  クラス共通のデータの初期化
    private static function init():void
    {
        ~アレやコレやアンなものまで、最初に計算して取って置く~
    }
    //  初期化処理
    init();
    ・・・
上記の場合、初めて hongege インスタンスが作成される際に init() が実行されるので、 初期化処理が重い場合、そこで引っかかる事になります。
素直に、Flash (又は air )の初期化時に「hongege.init();」と予めの実行をするべきでしょう。


具体的に状況を再現し、トレースで観察してみます。
classTest/test.as

package classTest 
{
    trace("test定義領域外");
    public class test
    {
        trace("test定義領域?");
        //  静的関数
        public static function init():void 
        {
            trace("test静的初期化関数");
        }
        //
        public function test() 
        {
            trace("testコンストラクタ");
        }
    }
}
trace("testパッケージ指定領域外");
メインで、以下のように記述し、実行してみた。

trace("test静的初期化関数を外部から実行");
test.init();

結果、以下のトレースを得る事が出来ました。
test静的初期化関数を外部から実行
test定義領域?
test定義領域外
testパッケージ指定領域外
test静的初期化関数

「test.init();」実行時が、testクラスへの初めてのアクセスになるので、ここでクラス定義が実行され、その後に初期化関数を実行しています。
処理順序が素直に予測しやすい為、問題があった場合の把握も容易になる事でしょう。


小細工的対処策

クラスの定義に一つ static 変数を用意します。

package classTest 
{
    trace("test定義領域外");
    public class test
    {
        trace("test定義領域?");
        //  静的変数
        public static var   initFlag:Boolean;
        //
        public function test() 
        {
            trace("testコンストラクタ");
        }
    }
}
trace("testパッケージ指定領域外");
メインの初期化処理に以下の記述を追加。

trace("test静的変数に書き込み");
test.initFlag = true;
トレース結果。
test静的変数に書き込み
test定義領域?
test定義領域外
testパッケージ指定領域外
無理やりですが、クラス定義処理を任意のタイミングで実行する事ができます。


そもそもクラスにアクセスがあればよいので、静的変数などがなくともクラス定義処理が実行可能です。

trace("testクラスそのものにアクセス");
var i:Class = test;
トレース結果。
testクラスそのものにアクセス
test定義領域?
test定義領域外
testパッケージ指定領域外


とにかく、クラスにアクセスがあって初めてクラス定義に処理が及ぶと言う事のようです。


通常クラスの静的初期化関数から、internal class を呼べない理由

ここまで読み進んでいただけたのなら、この理由はお分かりでしょう。
しかし、とりあえずガッチリ検証することに。

classTest/test.as

package classTest 
{
    trace("test定義領域外");
    public class test
    {
        trace("test定義領域?");
        //
        public static function init():void 
        {
            trace("test静的関数でfooインスタンス作成");
            var f:foo = new foo();
        }
        trace("test静的初期化関数実行");
        init();
        //
        public function test() 
        {
            trace("testコンストラクタ");
        }
    }
}
trace("testパッケージ指定領域外");
internal class foo
{
    trace("foo定義領域");
    //
    public function foo() 
    {
        trace("fooコンストラクタ");
    }
}
trace("foo定義記述後");
メインで、インスタンス作成。

trace("testクラスのインスタンス作成");
var t:test = new test();

このコードで実行すると・・・
testクラスのインスタンス作成
test定義領域?
test静的初期化関数実行
test静的関数でfooインスタンス作成
[Fault] exception, information=TypeError: Error #1009: null のオブジェクト参照のプロパティまたはメソッドにアクセスすることはできません。
Fault, test.as:11
internal class である foo のインスタンスを作成出来ません。
トレース結果からも判るように、「foo定義領域」がトレースされる前に「new foo();」の実行をしています。
正確には、「new foo();」で、foo クラス定義処理が始まらないのです。

試しに、以下のように test静的関数 書き換えて実行してみます。

public static function init():void 
{
    trace("test静的関数でfooクラスにアクセス");
    trace("foo:"+foo);
}

すると、このような結果に。
testクラスのインスタンス作成
test定義領域?
test静的初期化関数実行
test静的関数でfooクラスにアクセス
foo:null
foo定義領域
test定義領域外
testパッケージ指定領域外
foo定義記述後
testコンストラクタ
見事に null です。
foo のクラス定義処理をする為の、fooクラス の定義情報への参照?が初期化されていない」ので、「nullが云々~」とか怒られる事になる、と考える事が出来ます。



このような状況の対処法は、幾つかあります。

  1. まず、処理を弄りたくない場合、internal class を通常の package として表に出す事で、 回避できるようになります。

    classTest/test.as
    
    package classTest 
    {
        trace("test定義領域外");
        public class test
        {
            trace("test定義領域?");
            //
            public static function init():void 
            {
                trace("test静的関数でfooクラスにアクセス");
                trace("foo:"+foo);
            }
            trace("test静的初期化関数実行");
            init();
            //
            public function test() 
            {
                trace("testコンストラクタ");
            }
        }
    }
    trace("testパッケージ指定領域外");
    
    classTest/foo.as
    
    package classTest 
    {
        trace("foo定義領域外");
        public class foo
        {
            trace("foo定義領域");
            //
            public function foo() 
            {
                trace("fooコンストラクタ");
            }
        }
        trace("foo定義記述後");
    }
    trace("fooパッケージ記述外");
    

    結果。
    testクラスのインスタンス作成
    test定義領域?
    test静的初期化関数実行
    test静的関数でfooクラスにアクセス
    foo定義領域
    foo定義領域外
    foo定義記述後
    fooパッケージ記述外
    foo:[class foo]
    test定義領域外
    testパッケージ指定領域外
    testコンストラクタ
    
    「new foo();」で、foo クラス定義処理が実行されるようになりました。

  2. どうしても internal class で勧めたい場合は、「静的初期化関数を外部から実行」で対処する事になります。

    classTest/test.as
    
    package classTest 
    {
        trace("test定義領域外");
        public class test
        {
            trace("test定義領域?");
            //
            public static function init():void 
            {
                trace("test静的関数でfooクラスにアクセス");
                trace("foo:"+foo);
            }
        //  trace("test静的初期化関数実行");
        //  init();
            //
            public function test() 
            {
                trace("testコンストラクタ");
            }
        }
    }
    trace("testパッケージ指定領域外");
    internal class foo
    {
        trace("foo定義領域");
        //
        public function foo() 
        {
            trace("fooコンストラクタ");
        }
    }
    trace("foo定義記述後");
    
    メインからは、このように。
    
    trace("test静的初期化関数を外部から実行");
    test.init();
    

    この場合のトレース結果は以下のようになります。
    test静的初期化関数を外部から実行
    test定義領域?
    foo定義領域
    test定義領域外
    testパッケージ指定領域外
    foo定義記述後
    test静的関数でfooクラスにアクセス
    foo:[class foo]
    
    foo のクラス定義が先に実行されているので、null ではなく [class foo] とクラスオブジェクトになっている事がわかります。

結論

internal class のクラス定義処理の実行タイミングに注意。

また、クラス内に「静的関数を配置し、一度だけ実行する」という記述をする場合、 その実行タイミングに細心の注意を払う必要がある事が判った。


それっぽい考察

「ある処理が実行される際、一回だけ動作が引っかかる」「ある処理が初めて実行される時だけ動作が引っかかる」と言う現象は、全てクラス定義の実行タイミングと、静的関数による初期化処理にかかっていると言えるでしょう。


また、あまりないとは思いますが、うっかり巨大なクラスを作ってしまった場合にも、 そのクラス定義のタイミングによっては引っかかりが発生すると思われます。

単体のクラスで、バイトコードが数十メガとかのオーダーになるような事は、あまり現実的な話ではありません。しかし、クラス独自の構造体クラス、機能クラス、を多量に使用していて、それらのクラス定義実行処理が集中する場合は、それが非常に重い処理となりうるのです。
多量の internal class を使うのも、そのような可能性を上げてしまう要因になります。

シームレスなアニメーションをしながら、高機能&多機能のアプリケーションを開発する場合には、起こりうる話ですね。

そのような状況は、デザイン段階で予測が可能なハズなので、 上手い事「クラス定義処理」を分散させる工夫も必要になってくる事でしょう。


そんな時に、この記事の

var classPrototypeInitializeDummy:Class = 対象クラス;
の記述を思い出していただければ、幸いです。



トラックバック(0)

トラックバックURL: http://n-yagi.0r2.net/sanoupulurun/mt-tb.cgi/244

コメントする

ホーム >> 左脳Script >> Adobe Flash >> Action Script >> クラス定義と静的初期化処理のタイミング

アーカイブ

このブログ記事について

このページは、n-yagiが2009年12月26日 18:42に書いたブログ記事です。

ひとつ前のブログ記事は「今更、Flexコンポーネント で FLV Player を」です。

次のブログ記事は「クラス定義の管理方法」です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

Creative Commons License
このブログはクリエイティブ・コモンズでライセンスされています。