UIKitSeniorTechnical
Как работает жизненный цикл UIViewController? Опишите каждый lifecycle-метод.
Жизненный цикл: init → loadView → viewDidLoad (1 раз) → viewWillAppear → viewDidAppear → viewWillDisappear → viewDidDisappear (при каждом показе/скрытии) → deinit; UI настраивают в viewDidLoad, анимации запускают в viewDidAppear.
Жизненный цикл UIViewController
UIViewController проходит строго определённую последовательность методов при создании, отображении и уничтожении. Понимание этого цикла критично для корректной настройки UI, подписок и освобождения ресурсов.
Полная последовательность
class LifecycleViewController: UIViewController {
// 1. init — объект создан, view ЕЩЁ НЕ создан
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
// Инициализация свойств, НЕ обращаться к self.view
}
required init?(coder: NSCoder) { fatalError() }
// 2. loadView — создание view-иерархии (вызывается при первом обращении к self.view)
// Переопределяйте ТОЛЬКО если не используете XIB/Storyboard
override func loadView() {
// self.view = CustomRootView() // если нужен кастомный корневой view
super.loadView()
}
// 3. viewDidLoad — view создан и загружен в память ОДИН РАЗ за жизнь VC
override func viewDidLoad() {
super.viewDidLoad()
// Добавление subviews, настройка constraints, сетевые запросы
print("viewDidLoad: view.frame = \(view.frame)") // может быть (0,0,0,0)
}
// 4. viewWillAppear — view СЕЙЧАС появится на экране (каждый раз)
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Настройка Navigation Bar, обновление данных перед показом
// Вызывается при каждом возврате на экран (pop, dismiss)
print("viewWillAppear")
}
// 5. viewIsAppearing (iOS 17+) — view уже в иерархии, traits и геометрия известны
override func viewIsAppearing(_ animated: Bool) {
super.viewIsAppearing(animated)
// Первый метод где view.frame финальный; замена viewWillLayoutSubviews для позиционирования
}
// 6. viewWillLayoutSubviews — перед каждым layout pass
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
// Вызывается многократно; не выполняйте тяжёлые операции
}
// 7. viewDidLayoutSubviews — layout завершён, фреймы финальные
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// Корректировка фреймов, настройка cornerRadius на основе реального размера
someView.layer.cornerRadius = someView.bounds.height / 2
}
// 8. viewDidAppear — view полностью отображён (анимация завершена)
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Запуск анимаций, логирование аналитики, подписки на уведомления
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
}
// 9. viewWillDisappear — view СЕЙЧАС начнёт исчезать
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Сохранение состояния, остановка таймеров, скрытие клавиатуры
view.endEditing(true)
}
// 10. viewDidDisappear — view исчез с экрана
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
// Отписка от уведомлений, остановка аудио/видео
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil)
}
// 11. deinit — объект уничтожается, ARC освободил все strong ссылки
deinit {
print("\(type(of: self)) deinit") // Контроль утечек памяти
}
@objc private func keyboardWillShow() {}
}
Дополнительные методы
traitCollectionDidChange(_:)— изменился trait environment (тёмная/светлая тема, размер шрифта, Device).viewWillTransition(to:with:)— ротация устройства; используйте coordinator для синхронизации анимаций.didReceiveMemoryWarning()— система запрашивает освободить память; освободите кэши и большие объекты.
Порядок при push/pop
- Push: A.viewWillDisappear → B.viewWillAppear → B.viewDidAppear → A.viewDidDisappear
- Pop: B.viewWillDisappear → A.viewWillAppear → A.viewDidAppear → B.viewDidDisappear → B.deinit
Подводные камни
- Обращение к
self.viewвinit— это инициируетloadViewдосрочно, что ведёт к двойной инициализации или крашу. - Настройка constraints в
viewWillLayoutSubviews/viewDidLayoutSubviews— методы вызываются многократно, constraints добавляются повторно, растёт конфликт. - Тяжёлые синхронные операции в
viewDidLoadблокируют главный поток и задерживают появление экрана. - Не вызывать
superв lifecycle-методах — UIKit выполняет внутреннюю работу в super; пропуск ломает Navigation Bar, Safe Area и другие системные функции. - Подписка на NotificationCenter в
viewDidAppearбез отписки вviewDidDisappear— при каждом появлении экрана количество подписок растёт. - Использовать
view.frameвviewDidLoadдля позиционирования — фрейм ещё не финальный; используйтеviewDidLayoutSubviewsили Auto Layout. - Ожидать, что
deinitвызовется сразу послеviewDidDisappear— контроллер уничтожается только когда все strong-ссылки на него исчезают; retain cycle предотвращает deinit.
Common mistakes
- Сводить «работает жизненный цикл UIViewController? Опишите каждый lifecycle-метод.» к синтаксису и не объяснять responder chain.
- Игнорировать жизненный цикл, основной поток или момент освобождения ресурсов в сценарии uikit-3.
- Выбирать API по привычке, не проверяя состояние, ошибки, доступность и платформенные ограничения.
What the interviewer is testing
- Формулирует точную модель для «работает жизненный цикл UIViewController? Опишите каждый lifecycle-метод.» и подтверждает ее корректным примером.
- Умеет связать ответ с reuse pool, тестированием и отладкой на устройстве.
- Называет ограничения подхода uikit-3, включая производительность, память и сопровождение.