Вызываются ли методы жизненного цикла UIViewController при его инициализации (init)?

Ответ

Нет. Методы жизненного цикла, связанные с view (viewDidLoad, viewWillAppear и т.д.), не вызываются во время инициализации контроллера через init(nibName:bundle:) или init(coder:).

Последовательность событий

  1. Инициализация (init): Создается сам объект UIViewController. Его view (self.view) еще не загружена и равна nil.
  2. Загрузка View (ленивая): При первом обращении к свойству view (например, из кода или при добавлении контроллера в иерархию) система запускает процесс загрузки.
  3. Вызов методов жизненного цикла: Только после успешной загрузки view вызываются соответствующие методы.

Практический пример

class DemoViewController: UIViewController {
    // 1. Инициализация
    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
        print("1. init called. View is nil? (self.view == nil)") // true
    }

    // 2. Загрузка View (системный вызов)
    override func loadView() {
        print("2. loadView called")
        view = UIView() // Создаем view вручную
    }

    // 3. View загружена и готова
    override func viewDidLoad() {
        super.viewDidLoad()
        print("3. viewDidLoad called. View is nil? (self.view == nil)") // false
        // Здесь безопасно настраивать элементы интерфейса.
    }

    // 4. View появится на экране
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        print("4. viewWillAppear called")
    }
}
// Использование:
let vc = DemoViewController(nibName: nil, bundle: nil) // Выведет только "1. init..."
// vc.view // Раскомментировать для принудительной загрузки view и вызова 2, 3.

Важное следствие: Не пытайтесь обращаться к self.view или outlet-ам внутри init. Они будут nil. Вся настройка, зависящая от view, должна выполняться в viewDidLoad() или позже.

Ответ 18+ 🔞

Да ты посмотри, какая история-то интересная вырисовывается, блядь! Вот сидишь ты, такой весь из себя умный, думаешь: «Создам я контроллер, и сразу в init начну view настраивать, хуле». А нихуя!

Смотри сюда, распиздяйство полное. Эти все твои любимые методы — viewDidLoad, viewWillAppear и прочая хуйня — вообще не зовутся, пока ты контроллер через init создаёшь. Вообще. Тишина, блядь.

Как это работает, на самом деле? А вот как, слушай, не гони:

  1. init — это просто рождение голого тела. Ты вызвал init(nibName:bundle:). Система тебе выдала объект UIViewController. А его view, эта самая self.view — она в этот момент вообще nil, пустота, пиздец. Её нет. Ты к ней обратишься — тебе nil и выедет, и волнение ебать.

  2. View загружается, когда её тронули. Она, сука, ленивая, как мартышлюшка после обеда. Загрузится только тогда, когда кто-то впервые попробует к ней доступ получить. Либо ты в коде напишешь vc.view, либо система её сама запросит, когда контроллер на экран выводить будет.

  3. А вот тут уже начинается магия. Только после того, как view загрузили (в loadView), система такая: «Ага, ну теперь-то можно!». И начинает орать на все методы жизненного цикла. viewDidLoad — первый в этой очереди.

Пример, чтобы совсем пиздец понятно стало

class DemoViewController: UIViewController {
    // 1. Родили голое тело
    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
        print("1. init вызван. View есть? (self.view == nil)") // true, блядь, её нет!
        // Сюда пихать настройку интерфейса — это всё равно что хуй с горы кидать. Не попадёшь.
    }

    // 2. Система пошла view грузить
    override func loadView() {
        print("2. loadView вызван")
        view = UIView() // Вот тут мы её, родимую, создаём
    }

    // 3. О, ёпта! View загрузилась!
    override func viewDidLoad() {
        super.viewDidLoad()
        print("3. viewDidLoad вызван. View есть? (self.view == nil)") // false, ура!
        // Вот тут уже можно безопасно ковыряться в интерфейсе. Всё на месте.
    }

    // 4. А вот это уже когда на экран полезем
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        print("4. viewWillAppear вызван")
    }
}

// Используем:
let vc = DemoViewController(nibName: nil, bundle: nil)
// В консоли будет ТОЛЬКО: "1. init вызван. View есть? true"
// И ВСЁ. Тишина. viewDidLoad молчит, как Герасим немой.

// А вот если раскомментировать строчку ниже, тогда начнётся движ:
// let _ = vc.view // Система полезет view грузить, и в консоли появится 2, 3, 4.

И главный вывод, который бьёт, как кирпичом по ебалу: Не лезь ты в init с настройкой self.view или аутлетов. Они там — nil, пизда. Вся работа с интерфейсом должна быть в viewDidLoad() или позже, когда view уже точно есть. Иначе будешь потом охуевать, почему у тебя кнопки не отображаются, а в консоли краши летят.