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

Использование адаптивного popover segue и перенос адресата в контроллер навигации приводит к утечкам памяти

Скажем, у меня есть контроллер представления, который я показываю с помощью адаптивного popover segue при нажатии на кнопку. Теперь, в некоторых случаях, я могу захотеть обернуть контроллер представления назначения в (например) навигационном контроллере. Итак, я назначил себя делегатом для делегата popoverPresentationController и внедряю метод presentationController:viewControllerForAdaptivePresentationStyle:.


Но я заметил что-то странное: в некоторых случаях объекты не освобождались. Если в ранее упомянутом методе я обернул представленный диспетчер представлений в контроллере навигации:

func presentationController(controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
    return UINavigationController(rootViewController: controller.presentedViewController)
}

При увольнении контроллер навигации освобождается, но представленный контроллер представления остается выделенным.

Если, напротив, я сразу показываю контроллер навигации через адаптивный popover segue, а затем увольняю как контроллер навигации, так и контроллер данных, который он содержит, освобождаются правильно.


Для демонстрационных целей обратитесь к этому тестовому проекту (Swift): https://github.com/djbe/AdaptivePopoverSegue-Test

Что мы получаем при динамической упаковке в навигационном контроллере (коснитесь кнопки "Popover, nav автоматически добавлена" ):

--- Showing details ---
Loaded details view controller (0x7fab31632b70)
Loaded navigation controller (0x7fab32815600)
Deinit navigation controller (0x7fab32815600)

Как вы можете видеть, контроллер представления деталей никогда не освобождается.


Я проверил документацию для presentationController:viewControllerForAdaptivePresentationStyle:, но нет конкретных упоминаний о владении, сильных записях и т.д.... Я попытался использовать Инструменты с инструментом Allocations, но в этом (простом) случае так много удержаний/выпусков, что я не мог найти прямую проблему.

Кто-нибудь когда-либо сталкивался с этой проблемой? Или у вас есть идея о том, как решить эту проблему?


Решение

Как уже упоминалось ниже @TomSwift, есть ошибка, связанная с круговой ссылкой между контроллером и секцией. Единственный способ решить эту проблему и по-прежнему переносить контроллер назначения в контроллер навигации - это сделать обертку в методе init segue (custom).

Я обновил свой пример кода в Github, чтобы продемонстрировать, как это будет достигнуто с помощью решения, упомянутого в @Vasily, но все же допускает динамическое поведение переноса с использованием протоколов, не прибегая к хакерским обходным решениям с использованием NSUserDefaults.

4b9b3361

Ответ 1

Решение

Вам нужно создать пользовательский класс UIStoryboardSegue и переопределить функцию init.

Пример:

class StoryboardSegue: UIStoryboardSegue {

override init(identifier: String?, source: UIViewController, destination: UIViewController) {
    super.init(identifier: identifier, source: source, destination: NavigationController(rootViewController: destination))
}
}

Main.storyboard

введите описание изображения здесь

результат

введите описание изображения здесь

Ответ 2

Используя XCode8, я заметил, что существует круговая ссылка между DetailsViewController и UIStoryboardSegue. Я не вижу способа чистить этот цикл, поскольку он является внутренним для UIKit. Похоже, что вторичная круговая ссылка включает в себя NSDictionary ivar "_externalObjectsTableForLoading". Вы должны сообщить об этом Apple!

введите описание изображения здесь

Решение состоит в том, чтобы не повторно использовать DetailsViewController, предварительно загруженный секцией. Если вы вручную создадите экземпляр самостоятельно, вы можете обойти эту проблему. Здесь возможна реализация (требуется установить идентификатор восстановления в раскадровку!):

func presentationController(controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
    if (wrapInNavigationController) {
        let vc = controller.presentedViewController
        if let restorationIdentifier = vc.restorationIdentifier {
            return NavigationController(rootViewController: vc.storyboard!.instantiateViewControllerWithIdentifier(restorationIdentifier))
        }
    }
    return controller.presentedViewController
}