Жизненный цикл UIViewController

Рассмотрим когда вызываются методы контроллера и что можно делать внутри них. Когда настраивать вьюхи и данные.

Поможем в чате для iOS разработчиков

Читается за 2 минуты Обновлено 5 месяцев назад

Класс контроллера содержит view. Вы добавляете свои вью именно на эту корневую вью контроллера. Чтобы понять жизненный цикл, нужно знать, что:

View не создается с инициализацией контроллера

Контроллеру нужна причина, чтобы создать объект view. Концепция жизненного цикла строится вокруг этой особенности. Просто держите в уме, что view контроллера создаётся не сразу, а по необходимости.

 Про жизненный цикл UIViewController
Про жизненный цикл UIViewController

Инициализируем UIViewController

Рассмотрим UIViewController. Доступно два инициализатора:

override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
    super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
    
required init?(coder: NSCoder) {
    super.init(coder: coder)
}

Ещё есть инициализатор без параметров init(), но это обёртка над первым инициализатором.

На этом этапе контроллер инициализирует проперти и отрабатывает тело инициализатора. View не загружается, аутлеты не активны. В инициализаторе с nib сохраняется только имя файла, а сам файл не подгружается.

Загружаем View

Когда разработчик презентует контроллер, для системы это причина загрузить view. В контроллере есть методы жизненного цикла, с помощью которых мы следим за процессом и добавляем свою логику.

override func loadView() {}

Метод loadView() вызывается системой. Его не нужно вызывать вручную. Но можно переопределить, чтобы подменить корневую view. Если нужно загрузить view вручную (и вы уверены, что это нужно), то держите красную кнопку loadViewIfNeeded(). Флаг isViewLoaded показывает загружена view или нет.

Второй метод легендарен, как Стив Джобс. Он вызывается, когда view закончила загрузку.

override viewDidLoad() {
    super.viewDidLoad()
}

Разработчики не просто так настраивают контроллер и view-хи в методе viewDidLoad(). До вызова этого метода корневой view не существует, а после - контроллер готов появиться на экране. Во viewDidLoad() память под view выделена, view загружена и готова к настройкам.

View нельзя настраивать в инициализаторе: если вызывать controller.view - она загрузится. Но контроллер сейчас не виден, а может быть вообще никогда не покажется. Зря потратите память и займете главный поток

Проект от такого не развалится, но элементы интерфейса расходуют память — не нужно тратить её раньше, чем нужно. Делайте это по необходимости.

Раньше я делал проперти-вьюхи контроллера так:

class ViewController: UIViewController {
    
    let redView = UIView()
}

Но когда подготавливал статью, понял ошибку. Проперти инициализируется вместе с контроллером, а значит, память для view выделится сразу. Правильно отложить это до требования, пометьте проперти как lazy.

В методе viewDidLoad() размеры view-х неверные - привязываться к высоте и ширине нельзя. Делайте настройку, которая не зависит от размеров.

Есть метод viewDidUnload(). Корневая view может выгружаться из памяти, а это означает кое-что невероятное!

Метод viewDidLoad() может вызываться несколько раз

Если модальный контроллер закрыть, view выгрузится из памяти, но контроллер будет жив. Аутлеты здесь активны, но уже не имеют смысла — их можно ресетить. Если показать контроллер ещё раз, view загрузится снова. Если система выгрузила view, значит, у неё была причина. Не нужно обращаться к корневой view в этом методе — это загрузит view.

В вашем проекте ничего не сломается, viewDidLoad() несколько раз вызывается редко. Разделите настройку данных и view-х в следующем проекте.

Показываем и прячем View

Появление контроллера начинается с метода viewWillAppear:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
}
    
override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
}

Появление контроллера в модальном окне или переход в UINavigationController-e вызовут viewWillAppear до анимации, а viewDidAppear — после. При вызове viewWillAppear view уже находится в иерархии.

Оба метода в связке. Тут делать настройку не нужно, но можно спрятать или показать view-хи, или добавить несложное поведение. В методе viewDidAppear() начинайте сетевой запрос или крутите индикатор загрузки. Оба метода могут вызываться несколько раз.

Есть методы, которые сообщают, что view пропадает с экрана. Вот схема:

 Схема жизненного цикла ViewController
Схема жизненного цикла ViewController

Обратите внимание на пару антагонистов viewWillDisappear() и viewDidDisappear(). Они вызываются, когда view удаляется из иерархии представлений. Если вы показываете другой контроллер поверх, то методы не вызываются.

Layout

Методы лейаута привязаны к жизненному циклу view. Доступно 3 метода:

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()
}
    
override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
}

Первый метод вызывается до layoutSubviews() корневой view, второй - после. Во втором методе размеры корректные, а view размещены правильно — можно подвязываться к размерам корневой view.

Есть отдельный метод про изменение размеров view. Он вызывается и для поворота устройства:

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)
}

После него вызываются методы viewWillLayoutSubviews() и viewDidLayoutSubviews().

Кончилась память

Если вы не очистите объекты, из-за которых это происходит, iOS принудительно крашнет приложение. Этот метод - предупреждение, у вас есть шанс освободить немного памяти.

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
}
Поправить или дополнить статью через Pull Request