Ответ
Для сохранения контекста, связанного с UI или навигацией, используйте viewWillDisappear(_:). Для сохранения данных модели или состояния приложения — applicationWillResignActive в AppDelegate/SceneDelegate.
Почему viewWillDisappear(_:)?
Это последний гарантированный момент в жизненном цикле контроллера, когда он ещё находится в иерархии и его view видимы. Метод deinit не подходит для UI-операций, так как view к этому моменту уже удалены.
Пример сохранения навигационного состояния:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Проверяем, что контроллер действительно скрывается (а не показывается другой поверх)
if self.isMovingFromParent || self.isBeingDismissed {
// Сохраняем контекст
UserDefaults.standard.set(selectedItemId, forKey: "lastSelectedItem")
// Или передаем данные обратно через делегат/closure
delegate?.viewControllerWillClose(with: data)
}
}
Когда использовать deinit?
Только для очистки ресурсов самого контроллера (например, отписки от уведомлений, которые были добавлены в viewDidLoad). Не полагайтесь на него для сохранения данных, так как его вызов зависит от ARC и может быть отложен.
Лучшая практика: Разделяйте сохранение UI-состояния (в viewWillDisappear) и сохранение данных модели (в ответ на события жизненного цикла приложения).
Ответ 18+ 🔞
А, ну вот, опять этот вечный спор, где что сохранять, а? Сидишь такой, пишешь приложение, и тут бац — пользователь свернул его, а у тебя всё поехало, данные потерялись, и ты сидишь и думаешь: «Ну что за пиздец, я же всё сохранил, вроде?». А оказывается, ты сохранял не там, где надо, чувак!
Смотри, чтобы не было вот этого вот «ой, всё», запомни раз и навсегда, как таблицу умножения. Есть два священных места, куда ты должен класть свои сохранёнки, и они — не соседние полки в холодильнике.
Первое — это для всякой UI-хероты, типа какой пункт в списке выбран или на какую кнопку тыкали. Для этого у нас есть старый добрый viewWillDisappear(_:). Почему он? Да потому что это последний момент, когда твой контроллер ещё жив-здоров, вьюхи его на месте, и можно спокойно сказать: «Так, братва, запоминаем, что мы тут натворили».
Но, внимание, ебушки-воробушки! Не надо сохранять всё подряд каждый раз, когда показывается поп-овер или что-то поверх. Только когда контроллер реально уходит в небытие — его выпихивают из стека навигации или закрывают. Для этого есть проверочки.
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Смотри, не обосрись тут! Сохраняем только если нас реально выгоняют.
if self.isMovingFromParent || self.isBeingDismissed {
// Вот тут уже можно смело писать в UserDefaults или куда ты там привык
UserDefaults.standard.set(selectedItemId, forKey: "lastSelectedItem")
// Или пинать делегата: «На, мудак, забери данные обратно!»
delegate?.viewControllerWillClose(with: data)
}
}
Второе место — это для серьёзных дел, для данных модели, которые должны пережить даже если приложение накрылось медным тазом. Вот это уже не к контроллеру, а к AppDelegate или SceneDelegate. Там ловишь событие applicationWillResignActive. Пользователь нажал Home, получил звонок, или уведомление с котиком перекрыло экран — вот тут-то и надо срочно всё писать в базу, в файл, куда угодно, лишь бы не потерять. Потому что дальше приложение могут и прибить, а deinit твоего контроллера может и не дождаться, он же от ARC зависит, эта хитрая жопа.
А про deinit что? Его, блядь, только для уборки за собой используй! Отписался от нотификаций, которые в viewDidLoad навешал, почистил какие-то свои референсы — и всё. Полагаться на него для сохранения — это как надеяться, что ты вспомнишь пароль от вайфая, когда уже будешь падать с десятого этажа. Бесполезно и поздно.
Короче, золотое правило, чтобы не было волнения ебать:
UI-состояние (что на экране) — сохраняешь в viewWillDisappear(_:).
Данные приложения (что в голове) — сохраняешь в applicationWillResignActive.
А в deinit только мусор за собой выносишь. Всё, вопрос закрыт, можно идти пить чай.