Вопрос проверяет опыт работы с интерактивными переходами и способность строить устойчивую модель подписок, которая не ломается при отменённых жестах и частичных переходах.
При интерактивных переходах нельзя рассчитывать, что viewWillDisappear всегда парно закончится viewDidDisappear. Поэтому подписки, зависящие от видимости экрана, чаще снимают в viewDidDisappear, а включают в viewWillAppear. Для более точного контроля используют координатор переходов (transitionCoordinator) и коллбеки завершения, чтобы корректно обработать отмену. Также полезно иметь единый “контейнер” подписок, который можно атомарно включать и выключать.
Интерактивные переходы ломают “наивную” схему, потому что пользователь может начать уход со страницы и отменить его. В этот момент часть методов appearance уже вызвана, но переход не завершился.
При жестах типа:
swipe-to-back (interactive pop)
pull-to-dismiss (interactive dismiss)
Возможны ситуации:
viewWillDisappear вызвался (переход начался)
пользователь отменил жест
viewDidDisappear не вызвался, потому что экран фактически не исчез
Если ты снял подписки в viewWillDisappear, экран остался на месте, но уже “отписан”, и UI перестал обновляться.
Самая устойчивая схема для подписок, которые должны работать только на видимом экране:
Подписаться в viewWillAppear
Отписаться в viewDidDisappear
Почему так лучше:
viewDidDisappear означает, что экран реально исчез
если переход отменён, viewDidDisappear не будет, и подписки останутся активны
Иногда важно “приглушить” что-то в момент начала ухода:
остановить тяжёлые анимации
приостановить таймер
уменьшить частоту обновлений
Тогда можно использовать transitionCoordinator, чтобы корректно обработать отмену.
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
guard let coordinator = transitionCoordinator else { return }
// временно приостанавливаем активность на время жеста
// pauseUpdates()
coordinator.animate(alongsideTransition: nil) { context in
if context.isCancelled {
// жест отменили — вернуть всё обратно
// resumeUpdates()
} else {
// переход завершился — можно окончательно отключить
// окончательная отписка всё равно лучше в viewDidDisappear
}
}
}
Смысл:
в момент начала перехода можно временно изменить поведение
в completion нужно проверить isCancelled и откатить изменения
Чтобы не разносить логику по куче мест, полезно иметь явные “состояния активности”:
isActiveOnScreen = true
подписки включены
обновления UI разрешены
isActiveOnScreen = false
подписки выключены
ресурсы освобождены
Тогда методы жизненного цикла просто переключают состояние, а не содержат всю логику.
Выбор места зависит от того, что именно ты подписываешь.
Подписки, зависящие от видимости
включать: viewWillAppear
выключать: viewDidDisappear
Подписки, зависящие от существования контроллера
включать: при инициализации или в viewDidLoad
выключать: deinit
Подписки, зависящие от конкретного перехода
использовать: transitionCoordinator для обработки cancel/finish
При интерактивных переходах ориентируйся на факт ухода (viewDidDisappear) и используй transitionCoordinator, если нужно реагировать на начало перехода и корректно обрабатывать отмену. Это делает подписки устойчивыми к жестам и “недозавершённым” переходам.