注: 簡単なプロジェクトを作成しました —ストーリーボードでタイプを
UIButton
とCustomButton
の間で切り替えるとGCの動作がどのように変化するかがわかります。
MonoTouchガベージコレクターに頭を包み込もうとしています。
この問題は MT 4. で修正されたものですが、継承されたタイプの場合)に似ています。
それを説明するために、親と子の2つのビューコントローラについて考えてみます。
子のビューには、タップするとコンソールに書き込む単一のUIButton
が含まれます。
コントローラーのDispose
メソッドは例外をスローするため、見逃すことはほとんどありません。
子ビューコントローラーは次のとおりです。
_public override void ViewDidLoad ()
{
base.ViewDidLoad ();
sayHiButton.TouchUpInside += (sender, e) =>
SayHi();
}
}
void SayHi()
{
Console.WriteLine("Hi");
}
protected override void Dispose (bool disposing)
{
throw new Exception("Hey! I've just been collected.");
base.Dispose (disposing);
}
_
親ビューコントローラーは、子コントローラーを提示し、それを閉じてGCを実行するタイマーを設定するだけです。
_public override void ViewDidLoad ()
{
base.ViewDidLoad ();
var child = (ChildViewController)Storyboard.InstantiateViewController("ChildViewController");
NSTimer.CreateScheduledTimer(2, () => {
DismissViewController(false, null);
GC.Collect();
});
PresentViewController(child, false, null);
}
_
このコードを実行すると、子コントローラーがガベージコレクションされているため、ファイナライザーから呼び出されたChildViewController.Dispose()
内でクラッシュすることが予想されます。涼しい。
ストーリーボードを開き、ボタンの種類をCustomButton
に変更します。 MonoDevelopは、単純なUIButton
サブクラスを生成します。
_[Register ("CustomButton")]
public partial class CustomButton : UIButton
{
public CoolButton (IntPtr handle) : base (handle)
{
}
void ReleaseDesignerOutlets()
{
}
}
_
どういうわけか、ボタンの種類をCustomButton
に変更するだけで、ガベージコレクターをだまして、子コントローラーがまだコレクションの対象ではないと思わせることができます。
どうですか?
これは、MonoTouch(ガベージコレクション)が参照カウントの世界(ObjectiveC)に住まなければならないという不幸な副作用です。
何が起こっているのかを理解するために必要な情報がいくつかあります。
あなたの場合に起こることは、MonoTouch/ObjectiveCブリッジを通過するサイクルであり、上記のルールのために、GCはサイクルを収集できるかどうかを判断できません。
これが起こることです:
これで、参照カウントが2であるため、CustomButtonインスタンスが解放されないことがわかります。また、CustomButtonのイベントハンドラーに参照があるため、ChildViewControllerインスタンスは解放されません。
これを修正するためにサイクルを中断する方法はいくつかあります。
[1]これは、管理対象オブジェクトにユーザー状態が含まれている可能性があるためです。対応するネイティブオブジェクト(マネージドUIViewインスタンスなど)をミラーリングしているマネージドオブジェクトの場合、MonoTouchはインスタンスに状態を含めることができないことを認識しているため、マネージドコードにマネージドインスタンスへの参照がなくなるとすぐに、GCはそれを収集できます。後の段階でマネージドインスタンスが必要になった場合は、新しいインスタンスを作成するだけです。