UIPageViewController не выпускает последний отображаемый диалог

Я представляю простой UIPageViewController и добавляю к нему несколько действительно простых и глупых дочерних контроллеров представления. Когда UIPageViewController отклоняется, я удаляю все дочерние контроллеры представления, те, которые в настоящее время не отображаются (перечислены в ChildViewControllers), и отображаемый (перечислен в ViewControllers). Не отображаемые освобождаются, отображаемые нет.

Я разбил это на простой неудачный тест, поэтому я уверен, что дело не в содержании контроллеров дочернего представления или каких-то других проблемах, связанных с этим. Я понятия не имею, что его сохраняет.

Образец:

Мастер (представлен)

public class MasterDialog : UIPageViewController
{
    public event EventHandler OnDialogClosed;

    private UIBarButtonItem _backButton;

    public MasterDialog() :  base(
        UIPageViewControllerTransitionStyle.Scroll, 
        UIPageViewControllerNavigationOrientation.Horizontal, 
        UIPageViewControllerSpineLocation.None, 
        25)
    {
        _backButton = new UIBarButtonItem(UIBarButtonSystemItem.Cancel);
        _backButton.Clicked += Close;

        NavigationItem.SetLeftBarButtonItem(_backButton, false);
    }

    public override void ViewDidDisappear(bool animated)
    {
        base.ViewDidDisappear(animated);

        OnDialogClosed(this, EventArgs.Empty);
    }

    private void Close(object sender, EventArgs arguments)
    {
        _backButton.Clicked -= Close;

        NavigationController.DismissViewController(true, null);
    }

    protected override void Dispose(bool disposing)
    {
        base.Dispose(disposing);

        Console.WriteLine("Master disposed");
    }
}

Источник основных данных

public class DataSource : UIPageViewControllerDataSource
{
     public override UIViewController GetPreviousViewController(
        UIPageViewController pageViewController, UIViewController referenceViewController)
     {
         var detail = (DetailDialog)referenceViewController;

         if (detail.Page - 1 == 0)
             return null;

         return GetViewController(detail.Page - 1);
     }

     public override UIViewController GetNextViewController(
        UIPageViewController pageViewController, UIViewController referenceViewController)
     {
         var detail = (DetailDialog)referenceViewController;

         return GetViewController(detail.Page + 1);
     }

     public UIViewController GetViewController(int page)
     {
         return new DetailDialog(page);
     }
}

Деталь (ребенок)

public class DetailDialog : UITableViewController
{
    public int Page { get; private set; }

    public DetailDialog(int page) : base(UITableViewStyle.Plain)
    {
        Page = page;
    }

    public override void ViewDidLoad()
    {
        base.ViewDidLoad();

        Console.WriteLine("Detail init: " + Page + " / " + GetHashCode());

        var label = new UILabel();
        label.Text = "#" + Page;
        label.ContentMode = UIViewContentMode.Center;
        label.Frame = new System.Drawing.RectangleF(0, 100, 320, 50);
        label.BackgroundColor = UIColor.Green;

        Add(label);
    }

    protected override void Dispose(bool disposing)
    {
        base.Dispose(disposing);

        Console.WriteLine("Detail disposed: " + Page + " / " + GetHashCode());
    }
}

Вступительный диалог (отправная точка)

    public class StartDialog : UIViewController
    {
        private DataSource _dataSource;
        private MasterDialog _master;

        public StartDialog()
        {
            Title = "WTF";
        }

        public override void ViewDidLoad()
        {
            base.ViewDidLoad();

            var button = new UIButton(UIButtonType.Custom);
            button.SetTitle("Open", UIControlState.Normal);
            button.BackgroundColor = UIColor.Green;
            button.Frame = new System.Drawing.RectangleF(20, 150, 280, 44);

            Add(button);

            button.TouchDown += OpenMasterDialog;
        }

        private void OpenMasterDialog(object sender, EventArgs arguments)
        {
            _dataSource = new DataSource();

            _master = new MasterDialog();
            _master.DataSource = _dataSource;
            _master.OnDialogClosed += HandleOnDialogClosed;

            _master.SetViewControllers(
                new [] { _dataSource.GetViewController(1) }, 
                UIPageViewControllerNavigationDirection.Forward, 
                false, 
                null
            );

            NavigationController.PresentViewController(
                new UINavigationController(_master), 
                true, 
                null
            );
        }

        private void HandleOnDialogClosed(object sender, EventArgs e)
        {
            _dataSource.Dispose();
            _dataSource = null;

            Console.WriteLine("Before: " + _master.ChildViewControllers.Length +
                "/" + _master.ViewControllers.Length + ")");

            var childs = _master
                .ChildViewControllers.ToList()
                    .Union(_master.ViewControllers);

            foreach (UIViewController child in childs)
            {
                child.RemoveFromParentViewController();
                child.Dispose();
            }

            Console.WriteLine("After: " + _master.ChildViewControllers.Length + 
                "/" + _master.ViewControllers.Length + ")");

            _master.OnDialogClosed -= HandleOnDialogClosed;
            _master.Dispose();
            _master = null;
        }
    }

person asp_net    schedule 15.01.2014    source источник
comment
Из любопытства вы пробовали sgen и sgen+newrefcount для сборки мусора? Также у вас есть это где-нибудь в репо/.zip? У меня может быть немного свободного времени сегодня вечером, чтобы проверить это (и удовлетворить еще одну часть моего любопытства).   -  person poupou    schedule 16.01.2014
comment
Привет @poupou, я не пробовал ничего, кроме конфигурации по умолчанию для сборки мусора, так как я уже несколько недель борюсь с утечкой памяти и не хочу сталкиваться с какими-либо глупыми побочными эффектами из-за экспериментальных вещей (если вы скажете, что безопасно использовать, я бы попробовал). Вы можете скачать проект здесь: dropbox.com/s/9y22lsw2fityr2r/ Дайте мне знать, если бы вы нашли минутку, чтобы посмотреть на это, и если это ошибка или только я не прав :-).   -  person asp_net    schedule 16.01.2014


Ответы (1)


Я могу неправильно понять ваш код/намерение, но в данном случае мне кажется, что все почти в порядке. Во всяком случае, вот мои выводы ...

Detail disposed: 1 / 36217954
After: 0/1)

Строка № 2 показывает /1, что, как я полагаю, является проблемой. Это нормально, потому что вы повторно открываете контроллер представления, IOW код:

_master.ViewControllers.Length

вызывает селектор viewControllers на UIPageViewController. Это возвращает: «Контроллеры представления, отображаемые контроллером представления страницы.», который в этот момент все еще равен DetailDialog (даже если master больше не отображается).

Это не специфично для Xamarin, приложение ObjC будет возвращать тот же (собственный) экземпляр в тот же момент времени.

Объясняется - но до сих пор не освобождается потом, почему?

В рамках новой Dispose семантики< /a> управляемый объект сохраняется после Dispose до тех пор, пока он требуется нативной стороне (но без нативной ссылки, поэтому он может быть изначально освобожден и впоследствии выпущен на управляемой стороне).

В этом случае жизненный цикл собственного объекта еще не завершен (т. е. iOS все еще имеет ссылку на него), поэтому он остается живым на управляемой стороне.

        _master.Dispose();
        _master = null;

Это удаляет управляемые ссылки на _master, но опять же (как и выше) он не будет освобожден (и не будет DetailDialog), пока используется собственный экземпляр _master. (с нативными ссылками).

Итак, кто получил ссылку на _master?

      NavigationController.PresentViewController(
            new UINavigationController(_master), 

^ Это создает UINavigationController, и пока он жив, есть ссылки на другие.

Когда я избавляюсь от UINavigationController (я оставил его в поле), экземпляры Master* и Detail* исчезают из HeapShot.

    _nav.Dispose();
    _nav = null;
person poupou    schedule 16.01.2014
comment
Спасибо за ваши усилия! Я знаю о ситуации с GC и RC, поэтому я реконструирую все свое приложение, чтобы освободить все, что когда-либо было открыто. На данный момент я не избавляюсь от диалога последней детали (ваше предположение здесь было правильным), и звучит логично также удалить навигационный контроллер. Но я не заставляю его работать, размещение навигационного контроллера в моем HandleOnDialogClosed ничего не меняет. Не могли бы вы опубликовать свой модифицированный метод, чтобы я мог протестировать его? Спасибо. - person asp_net; 16.01.2014
comment
Конечно, gist.github.com/spouliot/72fd254a1d1223b0a5a0 t см. DetailDialog (после открытия/закрытия). Вы можете использовать фильтр (после съемки), чтобы подтвердить это. Если вы раскомментируете две строки (Dispose/null), HeapShot сообщит об этом. - person poupou; 16.01.2014
comment
Хорошо, я только что обновился до 7.0.6, и теперь этот неприятный DetailDialog исчез. Раньше: никак. К сожалению, я не помню, какая версия у меня была до этого. И последний вопрос: по какой причине вы поместили кнопку навигации в приватное поле? Это полезно в отношении GC? - person asp_net; 16.01.2014
comment
Это было для тестирования с newrefcount - в противном случае он вылетал (есть ошибка). - person poupou; 16.01.2014
comment
Рад, что я все равно не попробовал: D Спасибо за ваше время и помощь. - person asp_net; 16.01.2014
comment
FWIW это исправлено (еще не выпущено) и, как я и подозревал, решило проблему, не требуя каких-либо изменений кода. - person poupou; 13.02.2014