ViewController Lifecycle — это последовательность методов, которые UIKit вызывает автоматически при управлении экранами в iOS. По данным Apple Documentation, каждый UIViewController проходит через предсказуемый набор состояний: от создания View до её появления и скрытия. Понимание порядка и назначения этих методов — необходимое условие для стабильной работы iOS-приложения.
Главное
ViewController Lifecycle — это набор методов, которые UIViewController получает от UIKit на протяжении своего существования. Каждый экран в iOS-приложении последовательно проходит через этапы создания, загрузки View, появления на экране, исчезновения и освобождения памяти. UIKit автоматически вызывает соответствующие методы на каждом этапе, и разработчик переопределяет их, добавляя свою логику.
Архитектура UIViewController заложена в основу UIKit и остаётся актуальной даже в эпоху SwiftUI — многие проекты по-прежнему используют классический подход или гибридную архитектуру. Понимание Lifecycle позволяет предсказать, в какой момент доступны subviews, когда можно безопасно изменять layout и какие операции выполнять при появлении или скрытии экрана.
Каждый метод жизненного цикла имеет конкретное назначение: одни вызываются единожды за всё время существования контроллера, другие — при каждом появлении или исчезновении. Смешивание логики между методами приводит к трудноуловимым багам: утечкам памяти, некорректному обновлению данных и лишним запросам к сети.
Шесть методов образуют полный жизненный цикл UIViewController. Порядок их вызова фиксирован и не зависит от способа навигации — push, present или unwind segue следуют одному и тому же расписанию.
loadView — первый метод цикла, вызываемый, когда View контроллера ещё не существует. Если вы используете Storyboard, UIKit автоматически загружает View из xib-файла. При программном создании интерфейса вы переопределяете этот метод, назначая корневую View вручную. В большинстве проектов loadView не трогают — работа ведётся в viewDidLoad.
Переопределение loadView требуется только в специфических случаях: когда весь интерфейс создаётся кодом без Storyboard или когда корневая View должна быть нестандартного класса. Apple рекомендует не вызывать super.loadView при переопределении — вы полностью берёте создание View на себя.
override func loadView() {
view = UIView()
view.backgroundColor = .white
}
viewDidLoad — наиболее часто используемый метод цикла. Он вызывается один раз после того, как View загружена в память, но ещё не отображается на экране. Здесь настраивают subviews, заполняют таблицы данными, регистрируют ячейки и подписываются на уведомления, которые действуют всё время жизни контроллера.
Важная особенность: viewDidLoad не вызывается повторно при повторном показе экрана. Если вам нужно обновлять данные каждый раз при появлении — используйте viewWillAppear. В viewDidLoad размещайте только одноразовые операции, от которых зависит базовая конфигурация.
viewWillAppear вызывается каждый раз непосредственно перед тем, как View становится видимой для пользователя. Этот метод получает параметр animated, указывающий, происходит ли появление с анимацией. Здесь обновляют данные, перезагружают таблицы, настраивают NavigationBar и скрывают или показывают элементы в зависимости от состояния приложения.
Используйте viewWillAppear для синхронизации состояния между экранами: если пользователь мог изменить данные на предыдущем экране, этот метод — правильное место для обновления интерфейса. Каждый вызов viewWillAppear предшествует появлению экрана, даже при возврате с дочернего контроллера.
viewDidAppear оповещает, что View полностью появилась на экране и все анимации перехода завершены. К этому моменту экран готов к взаимодействию — пользователь видит полный интерфейс и может с ним работать. Этот метод подходит для запуска анимаций, которые должны начаться после появления, старта таймеров и трекинга показов аналитики.
В отличие от viewWillAppear, viewDidAppear гарантирует, что экран не только видим, но и полностью прорисован. Если вы запускаете анимацию в viewWillAppear, часть кадров может быть пропущена, так как UIKit ещё не завершил переход. Для плавных анимаций используйте viewDidAppear.
viewWillDisappear вызывается перед исчезновением View с экрана — при переходе на другой контроллер, закрытии модального окна или сворачивании приложения. Это правильное место для сохранения состояния, отписки от уведомлений, остановки активных процессов и освобождения ресурсов, которые не нужны, когда экран не видим.
Важно помнить: viewWillDisappear не гарантирует, что View в итоге исчезнет — жест может быть отменён. Поэтому критичные данные сохраняйте также в viewDidDisappear, который вызывается только после фактического исчезновения.
viewDidDisappear завершает цикл появления и исчезновения. Он вызывается после того, как View уже скрыта с экрана. В этом методе окончательно останавливают анимации, удаляют временные объекты и подтверждают сохранение данных, начатое в viewWillDisappear.
Этот метод также предшествует deinit контроллера — если ваш UIViewController уничтожается, viewDidDisappear будет последним методом Lifecycle перед вызовом deinit. Используйте его для финальной очистки, которая должна произойти до уничтожения объекта.
Последовательность вызова зависит от того, как именно появляется экран: впервые, при возврате назад или при модальном показе. Рассмотрим три основных сценария с точки зрения UIKit.
При первом появлении экрана UIKit проходит полный цикл создания: вызываются loadView, затем viewDidLoad, после чего стартует анимация появления. Во время анимации вызывается viewWillAppear, а после завершения — viewDidAppear. Это единственный сценарий, когда все методы от loadView до viewDidAppear срабатывают последовательно.
override func viewDidLoad() {
super.viewDidLoad()
print("viewDidLoad — View загружена в память")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("viewWillAppear — скоро появится")
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("viewDidAppear — экран полностью видим")
}
Когда пользователь возвращается на предыдущий экран, UIKit не вызывает viewDidLoad повторно — View уже загружена в память. Вместо этого срабатывают только viewWillAppear и viewDidAppear на возвращаемом экране, а на текущем — viewWillDisappear и viewDidDisappear. loadView и viewDidLoad пропускаются, так как экран уже существует в стеке навигации.
Модальный показ подчиняется тем же правилам: у нового контроллера вызывается полный цикл при первом появлении, а у текущего — viewWillDisappear и viewDidDisappear. При dismiss порядок обратный: у возвращаемого контроллера снова срабатывают viewWillAppear и viewDidAppear, а у скрываемого — завершающие методы. Это поведение единообразно для всех типов переходов в UIKit.
Рассмотрим четыре ключевых сценария, в которых понимание Lifecycle напрямую влияет на качество кода и пользовательский опыт. Для каждого сценария приведём пример с рекомендациями.
viewDidLoad — место для первичной настройки, которая не зависит от видимости экрана. Здесь настраивают collectionView, регистрируют nib-файлы для ячеек, создают data source и layout. Если вы загружаете данные из сети, в viewDidLoad лучше только инициировать запрос, а обновлять UI — в viewWillAppear, когда экран готов к отображению.
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(
MyCell.self,
forCellReuseIdentifier: MyCell.identifier
)
viewModel.loadInitialData()
}
Используйте viewWillAppear для синхронизации данных каждый раз при появлении экрана. Например, если пользователь мог изменить настройки на предыдущем экране, здесь обновляют отображаемые значения, перезагружают таблицу и корректируют состояние NavigationBar. Это гарантирует, что экран всегда показывает актуальные данные при любом сценарии навигации.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tableView.reloadData()
navigationController?.setNavigationBarHidden(false, animated: animated)
}
viewDidAppear идеально подходит для запуска анимаций, которые должны начаться после того, как пользователь увидел экран. Здесь же отправляют события аналитики: показ экрана, старт onboarding или начало воспроизведения видео. Запуск анимаций до завершения перехода приводит к дерганому интерфейсу — UIKit не успевает подготовить достаточное количество кадров.
В viewWillDisappear сохраняют черновики, останавливают таймеры и отписываются от NotificationCenter. Это последний момент, когда экран ещё видим и доступен для операций, требующих контекста пользователя. Для критичных данных дополнительно используют viewDidDisappear как страховку от отменённых жестов.
Неправильное использование методов жизненного цикла — один из самых частых источников багов в iOS-приложениях. Рассмотрим основные ошибки, которые допускают разработчики на разных этапах работы с UIViewController.
Первая ошибка — создание subviews в init или loadView при наличии Storyboard. Если вы используете Interface Builder, не переопределяйте loadView без необходимости. Создание View в loadView при наличии раскадровки приводит к игнорированию xib-файла и пустому экрану.
Вторая ошибка — подписка на клавиатурные уведомления в viewDidLoad без отписки. Если вы подписались на UIResponder.keyboardWillShowNotification, но не отписались при скрытии экрана, блок будет вызываться и после deinit контроллера — это утечка памяти с потенциальным падением приложения.
Третья ошибка — таймеры и сетевые запросы, запущенные до появления экрана. Загрузка изображений или выполнение анимаций, когда View ещё не видна — пустая трата ресурсов. Перенесите визуальные обновления в viewWillAppear или viewDidAppear.
Четвёртая ошибка — сохранение данных только в viewWillDisappear. При интерактивном pop-жесте пользователь может начать свайп и отменить его — метод вызвался, но экран не исчез. Дублируйте критичное сохранение в viewDidDisappear или в обработчике applicationDidEnterBackground.
Часто задаваемые вопросы
Один раз — после загрузки View в память. При повторных появлениях экрана viewDidLoad не вызывается. Если нужно пересоздать View, контроллер должен быть уничтожен и создан заново.
UIKit требует вызова super.viewDidLoad для корректной работы жизненного цикла. Без него возможны проблемы с обновлением layout и обработкой переходов. Всегда вызывайте super первым делом внутри метода.
Не рекомендуется. Если контроллер инициализирован из Storyboard, UIKit автоматически загружает View из xib. Переопределение loadView отменяет этот процесс, и ваша раскадровка будет проигнорирована.
Подписывайтесь в viewDidLoad или viewWillAppear, а отписывайтесь в viewWillDisappear или viewDidDisappear, используя слабую ссылку на self, чтобы избежать утечек памяти при замыканиях.
Force quit убивает процесс принудительно — UIKit не успевает вызвать методы Lifecycle. Для сохранения данных используйте уведомление UIApplication.willTerminateNotification в AppDelegate.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также