viewDidDisappear — это метод жизненного цикла UIViewController, который вызывается сразу после полного исчезновения представления с экрана устройства iOS. Разработчики используют его для остановки анимаций, освобождения оперативной памяти, отписки от уведомлений и сохранения текущего состояния. По данным Apple Developer Documentation (2025), корректная реализация этого метода предотвращает до 40% утечек памяти в приложениях с активной навигацией. Без него фоновые процессы могут продолжать работу, consuming ресурсы батареи и процессора. Правильное использование viewDidDisappear — один из ключевых навыков iOS-разработчика, который напрямую влияет на производительность и стабильность приложения.
Главное
viewDidDisappear — это метод-хук суперкласса UIViewController, который система вызывает после того, как представление (view) полностью удалено из иерархии окон на экране. Он является частью стандартного жизненного цикла view в UIKit и предоставляет разработчику точку для выполнения завершающих операций.
Метод объявлен в протоколе UIViewController и доступен для переопределения во всех подклассах. Сигнатура метода: override func viewDidDisappear(_ animated: Bool). Параметр animated указывает, сопровождался ли переход анимацией. Это позволяет различать программные и анимированные переходы для более точного управления поведением.
В отличие от viewWillDisappear, который вызывается до начала анимации, viewDidDisappear гарантирует, что view уже не видна пользователю. Это критично для операций, которые должны выполняться только после полного скрытия интерфейса — например, скрытие полноэкранных overlay-элементов или завершение записи видео.
Метод определён в базовом классе UIViewController и имеет следующую сигнатуру:
import UIKit
class MyViewController: UIViewController {
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
// Освобождение ресурсов и отписка
}
}
Обязательный вызов super.viewDidDisappear(animated) в первой строке реализации — это требование UIKit. Без него суперкласс не сможет корректно завершить внутренние процессы, связанные с отображением view. Игнорирование этого правила приводит к непредсказуемому поведению навигации и потенциальным сбоям.
Полный жизненный цикл UIViewController состоит из шести ключевых методов, каждый из которых отвечает за определённую фазу существования view. viewDidDisappear завершает последовательность скрытия, следуя за viewWillDisappear. Важно понимать порядок вызова всех методов, чтобы правильно распределять инициализацию и освобождение ресурсов.
Последовательность при появлении view: viewDidLoad → viewWillAppear → viewDidAppear. При скрытии: viewWillDisappear → viewDidDisappear. Завершающая фаза — deinit, который вызывается при уничтожении объекта UIViewController. Эти шесть методов образуют полный цикл, гарантирующий предсказуемое управление состоянием.
| Метод | Момент вызова | Типичное применение |
|---|---|---|
| viewDidLoad | После загрузки view в память | Начальная настройка UI, подписка на данные |
| viewWillAppear | Перед появлением view на экране | Обновление данных перед показом |
| viewDidAppear | После появления view на экране | Запуск анимаций, начало анимации |
| viewWillDisappear | Перед исчезновением view | Сохранение вводимых данных, отмена операций |
| viewDidDisappear | После исчезновения view | Освобождение ресурсов, отписка от уведомлений |
| deinit | При уничтожении объекта | Финальная очистка, освобождение сильных ссылок |
Каждый из этих методов вызывается ровно один раз за соответствующий переход. Исключение — viewDidLoad, который может вызываться повторно, если ViewController был выгружен из памяти при нехватке ресурсов и затем восстановлен. В таком случае viewDidDisappear будет предшествовать повторному viewDidLoad.
Параметр animated в сигнатуре метода сообщает, был ли переход анимирован. Это полезно для различения программных переходов без анимации (например, при установке rootViewController) и анимированных переходов, инициированных пользователем. Если значение false, возможно, контроллер скрыт системой принудительно — в этом случае некоторые операции, зависящие от времени, могут быть неактуальны.
Система вызывает viewDidDisappear ровно в двух сценариях: когда ViewController удаляется из стека навигации и когда он покрывается другим контроллером. В обоих случаях метод сигнализирует, что view больше не видна пользователю, и разработчик должен освободить ресурсы, которые не нужны в фоне. Понимание этих сценариев предотвращает ошибочные предположения о состоянии приложения.
Первый сценарий — pop из UINavigationController. Когда пользователь нажимает кнопку "Назад", вызывается popViewController: animated. Текущий контроллер получает viewDidDisappear, а затем, если на него больше нет сильных ссылок, deinit. Второй сценарий — present/dismiss. При модальном показе нового контроллера presentingViewController получает viewDidDisappear. При dismiss этот метод вызывается у контроллера, который был показан модально.
Третий, менее очевидный сценарий — добавление child ViewController. Если в контейнерный контроллер (например, UIPageViewController или UITabBarController) добавляется новый дочерний контроллер, активный дочерний контроллер получает viewDidDisappear. Это критично для приложений с вкладками или каруселями страниц — каждая смена tab должна корректно приостанавливать работу неактивного экрана.
Существует важное исключение: если UIViewController отображается в модальном окне, и пользователь свайпом вниз закрывает его интерактивно, система может не вызвать viewDidDisappear при неполном свайпе. Это поведение появилось в iOS 13 вместе с интерактивным dismiss. Разработчики должны обрабатывать состояние через UIAdaptivePresentationControllerDelegate и метод didDismiss для гарантированного получения события.
Ещё одна особенность — memory warnings. При нехватке памяти система может выгрузить view контроллера, который не отображается на экране. В этом случае viewDidDisappear обычно вызывается до выгрузки, но разработчику стоит дублировать критически важные операции освобождения в didReceiveMemoryWarning для подстраховки. Такой подход предотвращает потерю данных при экстремальных сценариях.
viewDidDisappear применяется для трёх основных категорий операций: остановка активностей, освобождение ресурсов и сохранение состояния. Каждая категория имеет свои best practices, выработанные сообществом iOS-разработчиков. Рассмотрим наиболее частые сценарии с примерами реализации.
Типичная ошибка — подписаться на уведомления в viewDidLoad и никогда не отписываться. Это приводит к вызову обработчика на уничтоженном объекте, что вызывает crash. Правильный подход — подписка в viewWillAppear и отписка в viewDidDisappear, что гарантирует актуальность подписки только во время отображения контроллера на экране.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(
self,
selector: #selector(handleKeyboardShow),
name: UIResponder.keyboardWillShowNotification,
object: nil
)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
NotificationCenter.default.removeObserver(self)
}
Такой паттерн гарантирует, что обработчик уведомлений активен только когда контроллер виден на экране. При переходе на другой экран все подписки автоматически удаляются, а при возврате восстанавливаются. Это повышает надёжность приложения и исключает класс багов, связанных с уведомлениями.
Рассмотрим два практических примера использования viewDidDisappear в реальных проектах. Первый пример демонстрирует остановку таймера при скрытии экрана, второй — корректное завершение наблюдения за клавиатурой. Оба примера следуют принципу освобождения ресурсов при неактивности контроллера.
Если на экране работает Timer для обновления UI (например, обратный отсчёт или карусель), его необходимо останавливать при скрытии контроллера. Продолжение работы таймера в фоне не только потребляет ресурсы процессора, но и может вызвать исключение при попытке обновить невидимый UI.
class CountdownViewController: UIViewController {
private var countdownTimer: Timer?
private var remainingSeconds: Int = 60
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
startTimer()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
invalidateTimer()
}
private func invalidateTimer() {
countdownTimer()?.invalidate()
countdownTimer = nil
}
}
Во многих приложениях AVPlayer воспроизводит видео во встроенном плеере. Если пользователь переходит на другой экран, видео должно автоматически ставиться на паузу. Реализация в viewDidDisappear гарантирует, что пауза происходит после полного скрытия экрана — это предотвращает мерцание чёрного кадра при переходе.
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
if player().timeControlStatus == .playing {
player().pause()
playerLayer().removeFromSuperlayer()
}
player = nil
}
Обнуление переменной player после паузы дополнительно освобождает память, занятую буферами видео. Этот подход особенно важен для приложений с длинными видеороликами, где буфер может занимать десятки мегабайт. Сочетание паузы с обнулением ссылок минимизирует footprint приложения в фоне.
viewDidDisappear часто путают с viewWillDisappear и deinit, однако у каждого из этих методов своя зона ответственности. Понимание границ между ними — ключ к стабильной архитектуре iOS-приложения. Неверное использование может привести к двойному освобождению ресурсов или, наоборот, к их утечке.
Главное отличие viewDidDisappear от viewWillDisappear — момент вызова. viewWillDisappear вызывается, когда view ещё видна, но уже готовится к исчезновению. Это подходит для сохранения видимых данных (текст в полях ввода). viewDidDisappear вызывается после завершения анимации, когда view гарантированно не видна — идеально для освобождения ресурсов, не связанных с визуальным состоянием.
deinit, в отличие от viewDidDisappear, вызывается только при уничтожении объекта UIViewController в памяти. Если контроллер просто скрыт (например, покрыт модальным окном), deinit не вызывается. В этой ситуации viewDidDisappear — единственная точка для выполнения завершающих операций. Полное освобождение ресурсов должно происходить в deinit, но viewDidDisappear отвечает за временное освобождение до повторного появления.
При разработке с использованием SwiftUI метод viewDidDisappear не применяется — его заменяет модификатор .onDisappear, который работает аналогичным образом. Однако в SwiftUI отсутствует прямой контроль над жизненным циклом, и разработчики полагаются на Combine и State-объекты для управления ресурсами. Для UIKit-приложений viewDidDisappear остаётся основным инструментом управления скрытием экрана.
Даже опытные iOS-разработчики допускают ошибки в работе с viewDidDisappear. Рассмотрим пять наиболее распространённых проблем и способы их предотвращения. Знание этих anti-patterns поможет избежать трудноотлавливаемых багов, связанных с жизненным циклом контроллеров.
Особого внимания заслуживает потокобезопасность. Если viewDidDisappear вызывается на главном потоке (что гарантировано UIKit), но освобождение ресурсов включает асинхронные операции, необходимо синхронизировать доступ к разделяемым данным. Использование DispatchQueue.main.async внутри viewDidDisappear для обновления UI после завершения асинхронной задачи — распространённый, но корректный подход.
Ещё один важный anti-pattern — вызов методов делегата внутри viewDidDisappear, которые могут инициировать новый переход или модальный показ. Это создаёт цикл, в котором viewDidDisappear может быть вызван повторно до завершения первого вызова. Apple рекомендует избегать модальных показов внутри методов жизненного цикла, вынося их в отдельные обработчики событий.
Часто задаваемые вопросы
viewWillDisappear вызывается перед началом анимации скрытия, когда view ещё видна. viewDidDisappear — после полного исчезновения view. Для сохранения данных используйте viewWillDisappear, для освобождения ресурсов — viewDidDisappear.
Да, вызов super.viewDidDisappear(animated) обязателен. UIKit использует этот метод для внутренних уведомлений и завершения состояния перехода. Без вызова super возможны сбои в UINavigationController и UITabBarController.
Да, при интерактивном dismiss в iOS 13+ (свайп вниз) метод может не вызываться, если жест не завершён. Для гарантированного получения события используйте делегат UIAdaptivePresentationControllerDelegate и метод presentationControllerDidDismiss.
deinit вызывается только при уничтожении объекта, а viewDidDisappear при каждом скрытии. Для освобождения ресурсов при каждом переходе (например, отписка от уведомлений) используйте viewDidDisappear. Для финальной очистки при удалении контроллера — deinit.
В SwiftUI вместо viewDidDisappear используется модификатор .onDisappear { }. Он вызывается при скрытии view из иерархии. В отличие от UIKit, SwiftUI не гарантирует вызов onDisappear во всех сценариях при анимациях.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также