Вопрос проверяет понимание контейнерных контроллеров и того, почему жизненный цикл у экранов в табах отличается от “обычного push”.
UITabBarController — это контейнер, который держит несколько child-контроллеров и переключает между ними отображение. При переключении табов активный контроллер получает viewWillDisappear/viewDidDisappear, а новый — viewWillAppear/viewDidAppear. При этом контроллеры табов часто создаются заранее, и их viewDidLoad может вызываться в моменты, которые не совпадают с первым “показом пользователю”. Это важно учитывать при инициализации, подписках и обновлении данных.
UITabBarController меняет восприятие жизненного цикла, потому что экран больше не управляется напрямую навигацией, а живёт внутри контейнера, который держит несколько контроллеров одновременно.
UITabBarController — контейнерный контроллер, который управляет набором дочерних UIViewController и показывает один из них, переключая активный таб.
Важно понимать, что tab bar:
хранит ссылки на все контроллеры табов
переключает видимость их view
может держать их в памяти долгое время
viewDidLoadviewDidLoad вызывается, когда view конкретного таба впервые понадобилась. Но “когда понадобилась” зависит от того, как ты построил приложение.
Если ты заранее создал контроллеры и система обращалась к их view (например, из-за доступа к view, настройки, прогрева UI), то:
viewDidLoad может произойти ещё до того, как пользователь реально нажал на таб
Если контроллеры создаются лениво (например, через фабрику) и view не трогают, то:
viewDidLoad будет ближе к первому реальному показу
Практический вывод:
viewDidLoad — не “экран показали”, а “view создана”
При переключении между вкладками порядок обычно такой:
У текущего контроллера:
viewWillDisappear(_:)
viewDidDisappear(_:)
У нового контроллера:
viewWillAppear(_:)
viewDidAppear(_:)
При этом:
viewDidLoad у нового контроллера будет только если его view ещё не создавалась
если view уже была, viewDidLoad не будет
Часто каждый таб содержит UINavigationController. Тогда жизненный цикл зависит от того, что именно происходит:
Переключение таба:
вызывает appearance у top controller внутри navigation stack выбранного таба
Push внутри таба:
обычный порядок viewWillDisappear/viewDidDisappear у предыдущего и viewWillAppear/viewDidAppear у нового
Важная деталь:
при возвращении в таб система покажет текущий top controller, а не root
Из-за долгоживущих контроллеров в табах возникают типичные ошибки:
Обновили данные в viewDidLoad, а таб открыли позже — пользователь видит устаревшее
Делаем сетевой запрос в viewDidLoad, а пользователь туда никогда не зайдёт — лишняя работа
Подписались на события в viewDidLoad, а отписка не сделана — утечки и дубли
Поэтому часто используют правило:
первоначальная настройка UI — viewDidLoad
обновление данных перед показом — viewWillAppear
тяжёлые операции по факту видимости — viewDidAppear
UITabBarController делает контроллеры более “долгоживущими”, и их viewDidLoad не равен “первый раз показали пользователю”. В табах критично разделять: создание UI, обновление данных и работу с ресурсами по стадиям appearance.