viewDidDisappear:方法本质、UIViewController生命周期及调用时机

作者: IT Sectr 发布日期: 2026-03-05 阅读时间: 9 分钟

viewDidDisappear — 是UIViewController的生命周期方法,在视图(view)从iOS设备屏幕上完全消失后立即调用。开发人员使用它来停止动画、释放RAM内存、取消通知订阅以及保存当前状态。根据Apple Developer Documentation(2025),正确实现此方法可防止具有主动导航的应用程序中高达40%的内存泄漏。没有它,后台进程可能继续运行,消耗电池和处理器资源。正确使用viewDidDisappear是iOS开发人员的关键技能之一,直接影响应用程序的性能和稳定性。

要点

  • viewDidDisappear — 生命周期的最终方法,在视图从屏幕消失后调用
  • 用于释放资源:停止计时器、隐藏加载指示器
  • 必须用于取消NotificationCenter订阅和KVO观察以避免内存泄漏
  • viewWillDisappear的不同之处在于它在过渡动画完成后调用
  • 不能替代deinit — deinit负责对象的最终销毁

什么是viewDidDisappear?

viewDidDisappear — 是UIViewController超类的钩子方法,系统在视图(view)从屏幕上的窗口层次结构中完全移除后调用。它是UIKit中标准视图生命周期的一部分,为开发人员提供执行完成操作的点。

该方法在UIViewController协议中声明,可在所有子类中重写。方法签名:override func viewDidDisappear(_ animated: Bool)。参数animated指示过渡是否伴随动画。这允许区分程序化过渡和动画过渡,以实现更精确的行为控制。

与在动画开始前调用的viewWillDisappear不同,viewDidDisappear保证视图对用户不再可见。这对于仅在界面完全隐藏后才应执行的操作至关重要——例如,隐藏全屏覆盖元素或完成视频录制。

签名和声明

该方法在基类UIViewController中定义,具有以下签名:

swift
import UIKit

class MyViewController: UIViewController {
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        // 释放资源和取消订阅
    }
}

在实现的第一行必须调用super.viewDidDisappear(animated) — 这是UIKit的要求。如果没有它,超类无法正确完成与视图显示相关的内部过程。忽略此规则会导致导航行为不可预测和潜在崩溃。

viewDidDisappear在UIViewController生命周期中的位置

UIViewController的完整生命周期由六个关键方法组成,每个方法负责视图存在的特定阶段。viewDidDisappear完成隐藏序列,在viewWillDisappear之后。理解所有方法的调用顺序对于正确分配初始化和资源释放非常重要。

视图出现时的顺序:viewDidLoadviewWillAppearviewDidAppear。隐藏时:viewWillDisappearviewDidDisappear。最后阶段——deinit,在UIViewController对象销毁时调用。这六个方法形成一个完整的循环,保证可预测的状态管理。

方法调用时机典型应用
viewDidLoad视图加载到内存后UI初始设置,订阅数据
viewWillAppear视图出现在屏幕前显示前更新数据
viewDidAppear视图出现在屏幕后启动动画,开始动画
viewWillDisappear视图消失前保存输入数据,取消操作
viewDidDisappear视图消失后释放资源,取消通知订阅
deinit对象销毁时最终清理,释放强引用

这些方法中的每一个对于相应的过渡都只调用一次。例外——viewDidLoad,如果ViewController因资源不足从内存中卸载然后恢复,可能会再次调用。在这种情况下,viewDidDisappear将在新的viewDidLoad之前。

与过渡动画的关系

方法签名中的animated参数指示过渡是否具有动画效果。这对于区分无动画的程序化过渡(例如,设置rootViewController时)和用户发起的动画过渡非常有用。如果值为false,控制器可能被系统强制隐藏——在这种情况下,某些依赖于时间的操作可能不相关。

何时调用viewDidDisappear

系统在两种情况下调用viewDidDisappear:当ViewController从导航堆栈中移除时,以及当它被另一个控制器覆盖时。在这两种情况下,该方法都表明视图对用户不再可见,开发人员应释放后台不需要的资源。理解这些场景可以防止对应用程序状态的错误假设。

第一种场景——从UINavigationController中弹出。当用户按下“返回”按钮时,调用popViewController: animated。当前控制器接收viewDidDisappear,然后,如果没有更多强引用指向它,则调用deinit。第二种场景——present/dismiss。当模态显示新控制器时,presentingViewController接收viewDidDisappear。当dismiss时,此方法在模态显示的控制器上调用。

第三种不太明显的场景——添加child ViewController。如果向容器控制器(例如UIPageViewController或UITabBarController)添加新的子控制器,活动子控制器会接收viewDidDisappear。这对于具有标签页或页面轮播的应用程序至关重要——每次标签切换都应正确暂停非活动屏幕的工作。

异常和不明显的情况

存在一个重要例外:如果UIViewController显示在模态窗口中,并且用户通过向下滑动交互式关闭它,系统可能在不完整滑动时不调用viewDidDisappear。此行为出现在iOS 13中的交互式dismiss中。开发人员应通过UIAdaptivePresentationControllerDelegate和didDismiss方法管理状态,以确保接收事件。

另一个特点——内存警告。在内存不足时,系统可能卸载未在屏幕上显示的控制器视图。在这种情况下,viewDidDisappear通常在卸载前调用,但开发人员应在didReceiveMemoryWarning中复制关键释放操作以确保安全。这种方法可防止极端场景中的数据丢失。

典型使用场景

viewDidDisappear用于三个主要操作类别:停止活动、释放资源和保存状态。每个类别都有iOS开发人员社区制定的最佳实践。让我们通过实现示例来查看最常见的场景。

  • 停止动画 — 调用CALayer的layer.removeAllAnimations(),停止UIView.animate块
  • 释放资源 — 置零大图像,重置缓存数据,关闭文件描述符
  • 取消通知订阅 — 从NotificationCenter.default中移除观察者,停止KVO观察
  • 保存进度 — 关闭编辑屏幕时将草稿写入CoreData或UserDefaults
  • 隐藏覆盖层 — 移除加载指示器、提示和弹出元素,这些不应在过渡后保留

示例:取消NotificationCenter订阅

一个常见错误——在viewDidLoad中订阅通知但从不取消订阅。这会导致处理程序在已销毁的对象上调用,从而引发崩溃。正确的方法是——在viewWillAppear中订阅,在viewDidDisappear中取消订阅,这保证仅在控制器显示在屏幕上时订阅才有效。

swift
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)
}

这种模式保证通知处理程序仅在控制器在屏幕上可见时处于活动状态。切换到其他屏幕时,所有订阅自动移除,返回时恢复。这提高了应用程序的可靠性,并消除了与通知相关的一类错误。

Swift代码示例

让我们看两个在实际项目中使用viewDidDisappear的实践示例。第一个示例演示了在屏幕隐藏时停止计时器,第二个示例——正确结束键盘观察。两个示例都遵循控制器不活动时释放资源的原则。

停止计时器

如果屏幕上运行着用于更新UI的Timer(例如倒计时或轮播),则需要在控制器隐藏时停止它。计时器在后台继续运行不仅消耗处理器资源,还可能在尝试更新不可见UI时引发异常。

swift
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中实现保证暂停发生在屏幕完全隐藏后——这可以防止过渡时黑屏闪烁。

swift
override func viewDidDisappear(_ animated: Bool) {
    super.viewDidDisappear(animated)
    if player().timeControlStatus == .playing {
        player().pause()
        playerLayer().removeFromSuperlayer()
    }
    player = nil
}

暂停后置零player变量还可以释放视频缓冲区占用的内存。这种方法对于长视频应用程序尤其重要,缓冲区可能占用数十兆字节。暂停与置零引用的结合最小化了应用程序在后台的内存占用。

viewDidDisappear与其他生命周期方法

viewDidDisappear经常与viewWillDisappear和deinit混淆,但每种方法都有自己的责任范围。理解它们之间的界限是稳定iOS应用程序架构的关键。错误使用可能导致资源的双重释放或相反,资源泄漏。

viewDidDisappear与viewWillDisappear的主要区别——调用时机。viewWillDisappear在视图仍可见但已准备消失时调用。这适用于保存可见数据(输入字段中的文本)。viewDidDisappear在动画完成后调用,当视图保证不可见时——非常适合释放与视觉状态无关的资源。

与viewDidDisappear不同,deinit仅在UIViewController对象在内存中销毁时调用。如果控制器只是被隐藏(例如,被模态窗口覆盖),deinit不会调用。在这种情况下,viewDidDisappear是执行完成操作的唯一点点。完全释放资源应在deinit中进行,但viewDidDisappear负责在重新出现之前的临时释放。

何时使用哪种方法

  • viewWillDisappear — 保存输入数据,发送关于过渡开始的分析数据
  • viewDidDisappear — 停止动画,取消通知订阅,隐藏覆盖元素
  • deinit — 最终释放大资源,关闭网络连接

在使用SwiftUI开发时,viewDidDisappear方法不适用——取而代之的是.onDisappear修饰符,其工作方式类似。然而,SwiftUI中缺乏对生命周期的直接控制,开发人员依赖Combine和State对象进行资源管理。对于UIKit应用程序,viewDidDisappear仍然是管理屏幕隐藏的主要工具。

实现中的常见错误

即使有经验的iOS开发人员在使用viewDidDisappear时也会犯错误。让我们看看五个最常见的问题及其预防方法。了解这些反模式有助于避免与控制器的生命周期相关的难以追踪的错误。

  • 遗漏super.viewDidDisappear — super调用对于UIKit的正确工作是强制性的,缺少它可能导致控制器内部状态破坏
  • 在viewDidDisappear中执行重操作 — 在viewDidDisappear中同步写入大数据会阻塞主线程并破坏过渡动画
  • 忘记取消通知订阅 — 如果在viewDidDisappear中没有调用removeObserver,处理程序可能在僵尸对象上激活,导致EXC_BAD_ACCESS
  • 重复取消订阅 — 移除已在其他地方移除的观察者会导致NSInternalInconsistencyException异常
  • 依赖调用顺序 — 在嵌套容器中,子控制器和父控制器中viewDidDisappear的调用顺序不保证

需要特别注意线程安全。如果viewDidDisappear在主线程上调用(由UIKit保证),但资源释放涉及异步操作,则需要同步对共享数据的访问。在viewDidDisappear中使用DispatchQueue.main.async在异步任务完成后更新UI——常见但正确的方法。

另一个重要的反模式——在viewDidDisappear内部调用可能启动新过渡或模态显示的委托方法。这创建了一个循环,其中viewDidDisappear可能在第一次调用完成前再次调用。Apple建议避免在生命周期方法中进行模态显示,将它们移到单独的事件处理程序中。

常见问题

viewDidDisappear与viewWillDisappear有什么区别?

viewWillDisappear在隐藏动画开始前调用,此时视图仍然可见。viewDidDisappear在视图完全消失后调用。保存数据使用viewWillDisappear,释放资源使用viewDidDisappear。

是否需要调用super.viewDidDisappear?

是的,调用super.viewDidDisappear(animated)是强制性的。UIKit使用此方法进行内部通知和完成过渡状态。没有super调用,UINavigationController和UITabBarController可能发生崩溃。

viewDidDisappear可能不被调用吗?

是的,在iOS 13+中的交互式dismiss(向下滑动)时,如果手势未完成,该方法可能不会被调用。为确保接收事件,请使用UIAdaptivePresentationControllerDelegate委托和presentationControllerDidDismiss方法。

哪个更好:viewDidDisappear还是deinit?

deinit仅在对象销毁时调用,而viewDidDisappear在每次隐藏时调用。每次过渡时释放资源(例如取消通知订阅)使用viewDidDisappear。移除控制器时进行最终清理使用deinit。

viewDidDisappear在SwiftUI中如何工作?

SwiftUI中,代替viewDidDisappear使用.onDisappear { }修饰符。它在视图从层次结构中隐藏时调用。与UIKit不同,SwiftUI不保证在动画期间所有场景中调用onDisappear。

总结

  • viewDidDisappear — 隐藏前生命周期的最后一个方法,在过渡动画完成后调用
  • 主要目的 — 释放资源,停止计时器和取消通知订阅
  • 必须调用super.viewDidDisappear以确保UIKit正常工作
  • 与viewWillDisappear的区别在于调用时机:动画之后,而非之前
  • 不能替代deinit — deinit在对象销毁时调用,viewDidDisappear在每次隐藏时调用
  • 不用于重量级同步操作 — 它们会阻塞主线程并破坏动画
  • iOS 13+中,需要通过UIAdaptivePresentationControllerDelegate进行额外处理以确保调用

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读