33

注:簡単なプロジェクトを作成しましたUIButton。ストーリーボード間およびCustomButtonストーリーボード内でタイプを切り替えると、GCの動作がどのように変化するかがわかります。

頭をMonoTouchガベージコレクターに巻き付けようとしています。
この問題はMT4.0で修正されたものと似ていますが、継承されたタイプがあります。

それを説明するために、親と子の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だけで、ガベージコレクターをだまして、子コントローラーがまだコレクションの対象ではないと考えさせることができます。

どうですか?

4

1 に答える 1

46

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

何が起こっているのかを理解するには、いくつかの情報が必要です。

  • すべての管理対象オブジェクト (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 はそれを収集できます。後の段階でマネージド インスタンスが必要になった場合は、新しいインスタンスを作成するだけです。

于 2012-10-24T23:00:36.897 に答える