どうしても解消しないメモリリークを追いかけていったら、アクティベーションオブジェクトにたどりついたので、
自分の理解を深めるためにまとめてみます。
長くなりそうなので、3回のエントリーに分けます。
- AS3のクロージャ
- アクティベーションオブジェクトとスコープチェーン
- アクティベーションオブジェクトによるメモリリーク
関数
リファレンスによると、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のクロージャには「メソッドクロージャ」と「関数クロージャ」があるということですね。
関数の分類を図にするとこんな感じです。
もうちょっと詳しくみていきます。
例えば以下のようなコードがあったとき、
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でのメソッドクロージャの導入は、この面倒を解決するためのものだと勝手に想像しています。
次回のエントリーへつづく