通常クラスの静的初期化関数から、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メンバ変数定義
constメンバ定義
const を付けて観察してみます。その為、必然的にnew(インスタンス作成)記述になります。
trace("test不変メンバ変数定義");
private const t:test = new test();
test不変メンバ変数定義
test定義領域?
test静的関数
test定義領域外
testパッケージ指定領域外
testコンストラクタ
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();
・・・
素直に、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
トレース結果からも判るように、「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コンストラクタ
「foo のクラス定義処理をする為の、fooクラス の定義情報への参照?が初期化されていない」ので、「nullが云々~」とか怒られる事になる、と考える事が出来ます。
このような状況の対処法は、幾つかあります。
- まず、処理を弄りたくない場合、internal class を通常の package として表に出す事で、
回避できるようになります。
classTest/test.as
classTest/foo.aspackage 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パッケージ指定領域外");
package classTest { trace("foo定義領域外"); public class foo { trace("foo定義領域"); // public function foo() { trace("fooコンストラクタ"); } } trace("foo定義記述後"); } trace("fooパッケージ記述外");
結果。「new foo();」で、foo クラス定義処理が実行されるようになりました。testクラスのインスタンス作成 test定義領域? test静的初期化関数実行 test静的関数でfooクラスにアクセス foo定義領域 foo定義領域外 foo定義記述後 fooパッケージ記述外 foo:[class foo] 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クラスにアクセス"); 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();
この場合のトレース結果は以下のようになります。
foo のクラス定義が先に実行されているので、null ではなく [class foo] とクラスオブジェクトになっている事がわかります。test静的初期化関数を外部から実行 test定義領域? foo定義領域 test定義領域外 testパッケージ指定領域外 foo定義記述後 test静的関数でfooクラスにアクセス foo:[class foo]
結論
「internal class のクラス定義処理の実行タイミングに注意。」また、クラス内に「静的関数を配置し、一度だけ実行する」という記述をする場合、 その実行タイミングに細心の注意を払う必要がある事が判った。
それっぽい考察
「ある処理が実行される際、一回だけ動作が引っかかる」「ある処理が初めて実行される時だけ動作が引っかかる」と言う現象は、全てクラス定義の実行タイミングと、静的関数による初期化処理にかかっていると言えるでしょう。また、あまりないとは思いますが、うっかり巨大なクラスを作ってしまった場合にも、 そのクラス定義のタイミングによっては引っかかりが発生すると思われます。
単体のクラスで、バイトコードが数十メガとかのオーダーになるような事は、あまり現実的な話ではありません。しかし、クラス独自の構造体クラス、機能クラス、を多量に使用していて、それらのクラス定義実行処理が集中する場合は、それが非常に重い処理となりうるのです。
多量の internal class を使うのも、そのような可能性を上げてしまう要因になります。
シームレスなアニメーションをしながら、高機能&多機能のアプリケーションを開発する場合には、起こりうる話ですね。
そのような状況は、デザイン段階で予測が可能なハズなので、 上手い事「クラス定義処理」を分散させる工夫も必要になってくる事でしょう。
そんな時に、この記事の
var classPrototypeInitializeDummy:Class = 対象クラス;

コメントする