web-dev-qa-db-ja.com

これはMonoTouchGCのバグですか?

注: 簡単なプロジェクトを作成しました —ストーリーボードでタイプをUIButtonCustomButtonの間で切り替えると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に変更するだけで、ガベージコレクターをだまして、子コントローラーがまだコレクションの対象ではないと思わせることができます。

どうですか?

33
Dan Abramov

これは、MonoTouch(ガベージコレクション)が参照カウントの世界(ObjectiveC)に住まなければならないという不幸な副作用です。

何が起こっているのかを理解するために必要な情報がいくつかあります。

  • (NSObjectから派生した)すべての管理対象オブジェクトには、対応するネイティブオブジェクトがあります。
  • カスタム管理クラス(UIButtonやUIViewなどのフレームワーククラスから派生)の場合、管理オブジェクトは、ネイティブオブジェクトが解放されるまで存続する必要があります[1]。ネイティブオブジェクトの参照カウントが1の場合、マネージドインスタンスがガベージコレクションされるのを防ぐことはできません。 参照カウントが1を超えるとすぐに、マネージドインスタンスがガベージコレクションされないようにします。

あなたの場合に起こることは、MonoTouch/ObjectiveCブリッジを通過するサイクルであり、上記のルールのために、GCはサイクルを収集できるかどうかを判断できません。

これが起こることです:

  • ChildViewControllerにはsayHiButtonがあります。ネイティブのChildViewControllerはこのボタンを保持するため、その参照カウントは2になります(マネージドCustomButtonインスタンスによって保持される1つの参照+ネイティブのChildViewControllerによって保持される1つの参照)。
  • TouchUpInsideイベントハンドラーには、ChildViewControllerインスタンスへの参照があります。

これで、参照カウントが2であるため、CustomButtonインスタンスが解放されないことがわかります。また、CustomButtonのイベントハンドラーに参照があるため、ChildViewControllerインスタンスは解放されません。

これを修正するためにサイクルを中断する方法はいくつかあります。

  • 不要になったら、イベントハンドラーを切り離します。
  • 不要になったら、ChildViewControllerを破棄します。

[1]これは、管理対象オブジェクトにユーザー状態が含まれている可能性があるためです。対応するネイティブオブジェクト(マネージドUIViewインスタンスなど)をミラーリングしているマネージドオブジェクトの場合、MonoTouchはインスタンスに状態を含めることができないことを認識しているため、マネージドコードにマネージドインスタンスへの参照がなくなるとすぐに、GCはそれを収集できます。後の段階でマネージドインスタンスが必要になった場合は、新しいインスタンスを作成するだけです。

46