Подтвердить что ты не робот

Это ошибка в MonoTouch GC?

Примечание: Я создал простой проект - вы можете видеть, как изменяются типы переключения между UIButton и CustomButton при изменении раскадровки Поведение GC.

Я пытаюсь получить мою голову, обернутую вокруг сборщика мусора MonoTouch.
Проблема аналогична той, которая исправлена ​​в MT 4.0, однако с унаследованными типами.

Чтобы проиллюстрировать это, рассмотрите два контроллера представления: родительский и дочерний.

Детский вид содержит единственный 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 достаточно, чтобы обмануть сборщика мусора в мозговой контроллер, который еще не имеет права на сбор.

Как это так?

4b9b3361

Ответ 1

Это неудачный побочный эффект MonoTouch (который собрал мусор), чтобы жить в мире с подсчетом ссылок (ObjectiveC).

Для получения информации о том, что происходит, есть несколько частей информации:

  • Для каждого управляемого объекта (полученного из NSObject) существует соответствующий собственный объект.
  • Для настраиваемых управляемых классов (полученных из классов инфраструктуры, таких как UIButton или UIView) управляемый объект должен оставаться в живых до тех пор, пока не будет освобожден нативный объект [1]. Способ, которым он работает, заключается в том, что, когда у нативного объекта есть счетчик ссылок 1, мы не препятствуем тому, чтобы управляемый экземпляр получал сбор мусора. Как только количество ссылок увеличивается выше 1, мы не позволяем управляемому экземпляру собирать мусор.

Что происходит в вашем случае - это цикл, который пересекает мост MonoTouch/ObjectiveC и из-за вышеприведенных правил GC не может определить, что цикл может быть собран.

Вот что происходит:

  • В вашем ChildViewController есть sayHiButton. Собственный ChildViewController сохранит эту кнопку, поэтому его счетчик ссылок будет равен 2 (одна ссылка, удерживаемая управляемым экземпляром CustomButton + одна ссылка, принадлежащая встроенному ChildViewController).
  • Обработчик события TouchUpInside имеет ссылку на экземпляр ChildViewController.

Теперь вы увидите, что экземпляр CustomButton не будет освобожден, потому что его счетчик ссылок равен 2. И экземпляр ChildViewController не будет освобожден, потому что обработчик события CustomButton имеет ссылку на него.

Существует несколько способов разбить цикл, чтобы исправить это:

  • Отсоедините обработчик событий, когда он вам больше не нужен.
  • Утилизируйте ChildViewController, когда он больше не нужен.

[1] Это связано с тем, что управляемый объект может содержать пользовательское состояние. Для управляемых объектов, которые зеркалируют соответствующий собственный объект (например, управляемый экземпляр UIView), MonoTouch знает, что экземпляр не может содержать какое-либо состояние, поэтому, как только управляемый код не ссылается на управляемый экземпляр, GC может его собрать. Если управляемый экземпляр требуется на более позднем этапе, мы просто создаем новый.