Bezdepbonusu7Программирование: статьи и ответы

Вопрос

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

Автор вопроса: asp_netОтветов: 1

Я представляю простой 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;
        }
    }
  • Из любопытства вы пробовали sgen и sgen+newrefcount для сборки мусора? Также у вас есть это где-нибудь в репо/.zip? У меня может быть немного свободного времени сегодня вечером, чтобы проверить это (и удовлетворить еще одну часть моего любопытства). poupou 16.01.2014
  • Привет @poupou, я не пробовал ничего, кроме конфигурации по умолчанию для сборки мусора, так как я уже несколько недель борюсь с утечкой памяти и не хочу сталкиваться с какими-либо глупыми побочными эффектами из-за экспериментальных вещей (если вы скажете, что безопасно использовать, я бы попробовал). Вы можете скачать проект здесь: dropbox.com/s/9y22lsw2fityr2r/ Дайте мне знать, если бы вы нашли минутку, чтобы посмотреть на это, и если это ошибка или только я не прав :-). asp_net 16.01.2014

iosobjective-cxamarin

Ответы

Ответ 1 · рейтинг 3 · poupou

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

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

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

_master.ViewControllers.Length

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

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

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

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

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

        _master.Dispose();
        _master = null;

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

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

      NavigationController.PresentViewController(
            new UINavigationController(_master),

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

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

    _nav.Dispose();
    _nav = null;

Перевод вопроса и ответов сообщества Stack Overflow, лицензия CC BY-SA. Оригинал на Stack Overflow.

Похожие материалы