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

Управление памятью/ресурсами с помощью MonoTouch и MonoTouch.Dialog

У меня есть приложение MonoTouch, у которого есть UITabBarController, причем каждая из вкладок является UINavigationController. Некоторые из них завершают UIViewController, который добавляет UITableView и UIToolbar, а другие переносят DialogViewController.

Я пока не уделял много внимания управлению памятью/представлением (я в основном работал в симуляторе), но по мере того как я начал тестировать на реальном устройстве, я заметил некоторые сбои из-за низкого (например, приложение прекращается, и я обнаруживаю из своего журнала, что DidReceiveMemoryWarning получил вызов до этого). В других случаях я замечаю, что длительные паузы в реакции приложения, которые я предполагаю, связаны с циклом GC.

До сих пор я предполагал, что каждый DialogViewController, который я нажимаю на стек nav, очищает свои представления и другие вещи, которые он выделяет, когда я его всплываю. Но я начинаю понимать, что это, вероятно, не так просто, и что мне нужно начать вызов Dispose() на вещи.

Существуют ли лучшие методы для управления ресурсами и памятью с помощью MonoTouch и MT.D? В частности:

  • Требуется ли вызывать Dispose на DialogViewController после его всплытия? Если да, то где это лучше всего сделать? (ViewDidUnload? DidReceiveMemoryWarning? Destructor?)
  • DVC автоматически удаляет объекты, подобные RootElement, которые ему переданы, или мне нужно беспокоиться об этом? Как насчет UIImages, который он загружает как часть рендеринга ячейки таблицы (например, StyledStringElement)?
  • Есть ли места, где я должен называть GC.Collect(), чтобы лучше выделять коллекции, чтобы не отвлекаться на отзывчивость, когда происходит GC?
  • Помогает ли сборщик мусора генерации с проблемами интерактивности и достаточно ли он достаточно для использования в производственном приложении? (Я считаю, что он все еще выставлен как "экспериментальный" в MonoDevelop 3.0.2/MT 4.3.3)
  • Что мне нужно сделать в DidReceiveMemoryWarning, чтобы уменьшить вероятность того, что iOS запустит мое приложение? Поскольку каждый невидимый контроллер представления, похоже, получает этот вызов, я предполагаю, что я должен очистить ресурсы этого контроллера представления... должен ли я делать то же самое, что и в ViewDidUnload?
  • Кажется, я не вызываю вызов ViewDidUnload (даже после получения DidReceiveMemoryWarning). На самом деле я не помню, чтобы когда-либо видел это в моем журнале. Если iOS всегда вызывал мою ViewDidUnload после DidReceiveMemoryWarning, я мог бы просто выполнить всю очистку в ViewDidUnload... Каков наилучший способ разделить ответственность очистки между ViewDidUnload и DidReceiveMemoryWarning?

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

Обновить: чтобы сделать вопрос более конкретным: после использования инструментов и профайлера Xamarin Heapshot мне ясно, что я утечка UIViewControllers, когда пользователь всплывает стек навигации. Рольф подал bug для этого, и у него есть два дубликата, так что это реальная проблема не только для меня. К сожалению, я не нашел хорошего обходного пути для утечки UIViewControllers - я не нашел подходящего места для вызова Dispose(). Естественное место для свободных ресурсов, выделенных ViewDidLoad, находится в сообщении ViewDidUnload, но оно никогда не вызывается на симуляторе, поэтому объем памяти продолжает расти. На устройстве я вижу DidReceiveMemoryWarning, но я не хочу использовать это как место для свободного доступа к моему контроллеру и его ресурсам, поскольку мне не гарантировано, что iOS фактически выгрузит мое представление и, следовательно, не гарантирует, что мой ViewDidLoad снова будет вызван либо (приводя к ViewDidAppear, который должен был бы защищать код от ситуаций, когда были расположены его основные ресурсы). Я хотел бы получить совет о том, как выйти из этого беспорядка...

4b9b3361

Ответ 1

Я провел пару дней в исходном коде MTD и в профилировщике. Хотя я все еще ищу общие рекомендации относительно того, какой лучший шаблон проектирования для реализации DidReceiveMemoryWarning и ViewDidUnload, у меня есть общие замечания, которые могут быть полезны кому-то:

  • MonoTouch.Dialog очень хорошо себя ведет. Это не утечка каких-либо ресурсов при обычном использовании. Он держит дерево управления под DVC.Root и каждый метод Dispose Element Dispose содержит базовый элемент управления UIKit. Вам даже не нужно беспокоиться об утилизации старого RootElement, если вы заменили DVC.Root - средство настройки свойств автоматически использует его для вас. В целом, MTD, похоже, не страдает от существенных проблем с памятью. Существует одно исключение - см. Ниже.
  • При создании собственных пользовательских элементов (например, MultilineEntryElement) обязательно переопределите метод Dispose (bool), удалив базовый элемент управления UIKit (например, UITextView) и связав метод базового класса Dispose(). Исходный код проекта Miguel MT.D github содержит множество хороших примеров. Все элементы реализуют стандартный шаблон Dispose (хотя они опускают деструктор/финализатор, который вызывает Dispose (false)).
  • При реализации настраиваемых контроллеров представлений обычно нет необходимости реализовывать Dispose в подклассах UIViewController, а также в классах TableView DataSource или Delegate. Когда контроллер просмотра получит GC'ed, он будет правильно вызывать Dispose по его ссылкам. Все ячейки, которые вы выделяете в DataSource, будут правильно настроены.
  • В качестве исключения для (3) - я столкнулся с неприятной проблемой при добавлении моего собственного поднабора в ячейку TableView. Этот подвью - это элемент управления, который я создал под названием "UICheckbox", который в конечном итоге наследует UIImageView, который имеет два UIImages (вкл. И выкл.) И публичное событие с именем Clicked. Я испытываю только проблему, когда обработчик события, который ссылается на элементы DataSource, подключен к этому событию (если обработчик события не ссылается на сам DataSource или контроллер, все хорошо). Однако, когда выполняются вышеприведенные условия, и контроллер отклоняется, по-видимому, существует некоторый цикл, который GC не может понять, и каждый UICheckbox, который я помещал в TableView, просочился (вместе с его изображениями). Единственный способ, с помощью которого я работал, это добавить код в ViewDidDisappear, чтобы избавиться от ViewController и очистить его IFF состояния, он больше не находится в стеке навигации. Он взломан, но работает.
  • В общем, я придерживаюсь следующего шаблона для размещения объектов в контроллерах моего представления:

    • ничего не выделять в конструкторе (используйте его только для передачи состояния)
    • создайте дерево управления в ViewDidLoad (и удалите его в ViewDidUnload). подумайте "InitializeComponent" в XAML (если это поможет). Если UIViewController собирается нажимать DialogViewController на стек навигатора, ViewDidLoad является хорошим местом для создания DVC.
    • инициализировать значения в дереве управления в ViewDidAppear. Например. вы можете добавлять/удалять/заменять элементы, разделы и даже корень DVC в этом методе. Но не создавайте новый DVC.
  • Существует общая проблема с утечкой ViewControllers, когда пользователь переводит навигационный стек (я ссылаюсь на ссылку bugzilla в "Обновить" в вопросе). Это также влияет на MT.D. Существует довольно простой способ обхода - добавьте следующую строку кода в ViewDidAppear родительского контроллера представления:

        // HACK: touch the ViewControllers array to refresh it (in case the user popped the nav stack)
        // this is to work around a bug in monotouch (https://bugzilla.xamarin.com/show_bug.cgi?id=1889)
        // where the UINavigationController leaks UIViewControllers when the user pops the nav stack
        int count = this.NavigationController.ViewControllers.Length;
    

Рольф отлично справляется с объяснением, почему эта ошибка возникает и почему обходной путь работает в ссылке bugzilla, поэтому я не буду повторять ее.

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

Обновление: Еще несколько заметок:

  • Теперь я понимаю протокол для DidReceiveMemoryWarning и ViewDidUnload: первый всегда доставляется к каждому контроллеру представления, в то время как последний отправляется только для контроллеров представления, которые в настоящее время не отображаются, И не глубже, чем корень навигации стек. В конце концов, я решил игнорировать DidReceiveMemoryWarning, потому что на самом деле у меня нет изображений, которые я кэширую и могу сбросить (согласно руководству iOS). В ViewDidUnload я освобождаю все ресурсы, которые я выделил в ViewDidLoad.
  • В моем приложении есть TabBar, где на каждой вкладке находится UINavigationController, большая часть которого вызывает DialogViewController. Одной из проблем, с которыми я столкнулся, является утечка DialogViewController после того, как ViewDidUnload отпустил ссылку на него. Я попробовал Disposing DVC в ViewDidUnload, но iOS продолжал настаивать на его повторении, и я получал исключение для вызова селектора на объект GC. Я обнаружил причину - контроллер навигации держался на DVC в массиве ViewControllers. Решение состоит в том, чтобы освободить массив, создав вместо него массив нулевой длины - в ViewDidUnload:

    this.ViewControllers = new UIViewController[0];
    

Старый массив теперь будет GC'ed, так же как и DVC, потому что больше ничего не указывает на него. И iOS никогда не будет повторно запускать объект. Примечание - нет необходимости вызывать Dispose на DVC.