Вопрос проверяет понимание жизненного цикла, утечек памяти и того, почему “один раз подписался” часто ломает поведение при повторных показах экрана и интерактивных переходах.
viewDidLoad вызывается обычно один раз, но экран может появляться и исчезать много раз. Если подписаться в viewDidLoad, события будут приходить даже когда экран не виден, а отписка “в другом месте” легко забудется или не отработает в edge-кейсах. Это приводит к утечкам, дублям обработчиков и багам с состоянием. Подписки обычно привязывают к viewWillAppear/viewDidDisappear или к более явному жизненному циклу объекта.
Опасность в том, что viewDidLoad относится к моменту создания view, а подписки относятся к моменту активности экрана. Эти вещи не совпадают по времени.
viewDidLoad:
вызывается, когда view создана
может случиться задолго до первого показа
обычно не повторяется
подписка:
должна быть активна, пока экран “живой” или “видимый”
часто должна включаться и выключаться много раз
Если подписался в viewDidLoad, а экран:
ушёл в background
перекрылся модалкой
был скрыт другим экраном
то события всё равно могут продолжить приходить.
Дубли обработчиков
если подписка где-то повторяется (например, при пересоздании view), можно случайно получить несколько одинаковых подписок
результат: обработчик срабатывает 2-3-10 раз
Утечки памяти
подписка удерживает объект (прямо или косвенно)
контроллер не деинициализируется, потому что “на него кто-то подписан”
События приходят в невидимый экран
UI обновляется, когда пользователь экран не видит
возможны краши из-за обращения к состоянию, которое уже неактуально
Сложные сценарии переходов
интерактивные dismiss/pop могут быть отменены
методы жизненного цикла могут вызываться не в ожидаемой паре
В таких сценариях “подписался в didLoad, отписался в willDisappear” легко даёт расхождение.
NotificationCenter
NotificationCenter.default.addObserver(
self,
selector: #selector(handle),
name: UIApplication.didEnterBackgroundNotification,
object: nil
)
// если не удалить observer — проблемы (утечки, неожиданные вызовы)
KVO / наблюдение
если не снять наблюдение, можно получить краш при деинициализации
Combine / Rx
подписки живут дольше экрана, если их не очищать
Чтобы не ловить баги, полезно разделять подписки на типы:
Подписки, которые нужны пока экран виден
включать в viewWillAppear
выключать в viewDidDisappear
Подписки, которые нужны пока существует контроллер
подписываться при создании (или в viewDidLoad)
обязательно отписываться в deinit
Подписки, которые должны переживать экраны
выносить в сервисы / менеджеры, а не держать во вью-контроллере
Идея: делать жизненный цикл подписки симметричным.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// subscribe()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
// unsubscribe()
}
deinit {
// на всякий случай: cleanup, removeObserver, cancel tokens
}
Подписки в viewDidLoad опасны, потому что viewDidLoad почти не повторяется, а “активность экрана” повторяется постоянно. Привязывай подписки либо к появлению/исчезновению (viewWillAppear/viewDidDisappear), либо к жизни объекта (deinit) — в зависимости от смысла.