Пользовательский интерфейс зависает после повторного входа в Detail-VC во время URLSession Background-DownloadTask

Использование Swift-4.0.3, iOS-11.2, Xcode-9.2, iPhone-6S (или Simulator-10.0)

Примерно через неделю я пытаюсь выяснить, в чем проблема со следующей проблемой: пользовательский интерфейс зависает всякий раз, когда вы повторно входите в ViewController, который включает URLSession-Background-DownloadTask. Под повторным входом я имею в виду: переход от VC к Detail-VC и обратно к VC... и затем RE-ENTER во второй раз из VC к Detail-VC.

Мой пример проекта можно найти здесь на github: https://github.com/korners/Test00008

В примере проекта используется MZDownloadManager от mzeeshanid. Я пробовал и другие фреймворки - та же проблема. MZDownloadManager — это просто очень хорошая реализация, которую я нашел.

Вернемся к проблеме: теперь - что касается первой записи Detail-VC: все работает гладко, как и должно (без проблем). Даже закрытое приложение будет плавно запускаться в уже запущенную background-downloadTask (без проблем - т.е. ProgressBar и метки пользовательского интерфейса хорошо обновляются).

Но если пользователь нажимает кнопку «Назад» на Detail-VC (верхняя панель NavigationController) — с этого момента Detail-VC можно увидеть только в замороженном состоянии по умолчанию! (нет Обновления пользовательского интерфейса или движения progressBar больше не нужны).

Я высоко ценю любую помощь в этом!

P.S. Кстати, проблемы создает не Segue как таковой. Я также попытался создать экземпляр VC из раскадровки - и то же самое: ПОВТОРНЫЙ ВХОД замораживает пользовательский интерфейс:/


person iKK    schedule 06.03.2018    source источник


Ответы (2)


Да, я думаю, вы правильно поняли.

В моем случае у меня был VC на основе списка (давайте назовем его родительским VC) и подробный VC. Detail VC создаст фоновый сеанс и назначит себя его делегатом. Я также заметил, что если я выйду и снова войду в детализацию VC, что-то будет не так. Я пришел к тому же выводу, что сессия давала обновления первой детали VC, той, которую я, хотя и пропал, убил, когда я вернулся из нее.

Видите ли, первая деталь VC создает сеанс как свойство, поэтому он имеет указатель на сеанс. Он устанавливает себя в качестве делегата в сеансе, поэтому теперь сеанс имеет указатель на него. Это называется циклом и является причиной утечек памяти. Вот почему первая деталь VC не убивается - сессия все еще указывает на нее. Обычно это исправляется наличием слабой ссылки в одном направлении, но, как вы уже обнаружили, здесь есть еще одна проблема: вы не можете просто обойти и создать более одного фонового сеанса с одним и тем же идентификатором. Вам нужно создать его и сохранить как синглтон.

person Nemanja Kovacevic    schedule 20.03.2018
comment
Спасибо, Неманья, за подтверждение. Теперь он работает гладко как синглтон :) - person iKK; 20.03.2018

После еще одной отладки я понял, что при повторном входе в Detail-VC делегатом URLSesssion является nil.

Как выясняется (если настроено как фоновая задача) - тогда URLSession, все еще существующий из предыдущей загрузки, не создает новый объект URLSession, а возвращает существующий С ПРИКРЕПЛЕННЫМ СТАРЫМ ОБЪЕКТОМ ДЕЛЕГАТА !!!

То, как был настроен проект: нажатие кнопки «Назад» (или выход из текущего Detail-VC) отбросит все формы этого ViewController (включая его свойство downloadManager и его делегат). Следовательно, при повторном входе в Detail-VC сеанс URLSession, запущенный из предыдущего запуска, попытается отправить свои методы делегата делегату URLSession, которого больше не существует !!! (т.е. убитый при выходе из Detail-VC).

Интересно, что если приложение закрыто и перезапущено, делегат сеанса Background-URL может быть установлен заново. Очевидно, что удержание делегата происходит только до тех пор, пока приложение работает.

Подумав вслух, ... один из способов сделать это, я думаю, - это, например, сохранить объект View живым (и передать его фоновому URLSession в качестве его делегата), а затем представить этот объект View всякий раз, когда есть нужно (с addsubView(View) или подобным). Но в целом плохая идея иметь уникальные View-объекты во всем приложении.

Я полагаю, что лучший способ сделать это - создать объект singleton-URLSession в первую очередь (который выживает повсюду и не подвержен влиянию каких-либо переходов навигационного контроллера). И, следовательно, его делегат также сохраняется во всем приложении.

Мое главное сообщение на данный момент: БУДЬТЕ ОСТОРОЖНЫ, КТО ВЫ НАЗНАЧАЕТЕ ФОНОВОЙ-URLSESS В КАЧЕСТВЕ ДЕЛЕГАТА И ЧТО С НИМ ДЕЛАЕТ С НИМ ВАШЕ ЖИЗНЕННЫЙ ЦИКЛ ПРИЛОЖЕНИЯ (он не отпустит его так просто — не даже если объект URLSession (и его делегат вместе с ним) мертв. ;) Он отпустит его, только если вы закроете приложение и перезапустите)

Надеюсь, я все правильно понял?

Любые предложения или комментарии по этому вопросу высоко ценятся!

person iKK    schedule 07.03.2018