.onDisappear:工作原理及在SwiftUI中的应用

作者: IT Sectr 发布日期: 2026-06-26 阅读时间: 10 分钟

.onDisappear — 一个SwiftUI修饰符,当View从界面层次结构中移除时执行闭包。调用发生在关闭屏幕、切换标签页、dismiss模态窗口或滚动元素超出可见区域时。根据Apple Developer Documentation(2026),onDisappear在应用程序紧急终止或被system-watchdog销毁的场景下不保证调用。关于SwiftUI的更多信息,请阅读SwiftUI相关材料

要点

  • .onDisappear — 用于在View消失时执行代码的SwiftUI修饰符。
  • 对称性 — onDisappear与onAppear成对,以相反顺序调用。
  • 资源清理 — 主要场景:取消定时器、关闭连接、保存状态。
  • 分析 — 通过onDisappear发送screen close、dwell time、session end事件。
  • 无保证 — 应用程序紧急终止时不会调用onDisappear。

什么是.onDisappear?

.onDisappear — SwiftUI中的一个View修饰符,接受Void闭包并在View从层次结构中移除时执行。与.onAppear一起构成屏幕的完整生命周期:出现 — 运行 — 消失。Apple在iOS 13中随SwiftUI的发布引入了onDisappear,作为UIKit中viewDidDisappear的对应物。

在语法上.onDisappear与onAppear相同:修饰任何View并附加一个闭包,该闭包在视图被移除时由SwiftUI组件调用。与UIKit不同,viewDidDisappear仅在过渡动画完成后才触发,SwiftUI中的onDisappear可能在动画完成之前被调用——在View被标记为移除的时刻。

onDisappear语法

基本语法onDisappear与onAppear一样简洁。修饰符不接受额外参数——仅接受在主线程同步执行的闭包。

swift
struct DetailView: View {
    var body: some View {
        Text("详情屏幕")
            .onDisappear {
                print("DetailView已从屏幕上消失")
            }
    }
}

.onDisappear如何工作

调用机制onDisappear与onAppear相反:首先子元素接收onDisappear,然后是父元素。这种child-first规则保证子元素的资源在父元素资源之前被释放。如果子元素依赖于父元素的数据,它必须能够在没有父元素上下文的情况下正确终止。

SwiftUI在View被排除出渲染图时调用onDisappear。触发器可以是:从NavigationStack中pop、切换TabView、dismiss模态窗口(sheet/fullScreenCover)、条件显示View的变化(if/switch)。在List和ScrollView中,当单元格滚动出prefetch缓冲区时调用onDisappear。

Child-first调用顺序

Child-first顺序意味着如果VStack中有三个子View,onDisappear将首先以相反顺序为每个子元素调用,然后为父元素调用。这对于正确清理至关重要:子定时器在父ViewModel释放共享资源之前被取消。

swift
struct ParentView: View {
    var body: some View {
        VStack {
            ChildView(id: "A")
            ChildView(id: "B")
        }
        .onDisappear {
            print("Parent onDisappear — 最后")
        }
    }
}

struct ChildView: View {
    let id: String
    var body: some View {
        Text("子节点 \(id)")
            .onDisappear {
                print("子节点 \(id) onDisappear")
            }
    }
}

控制台输出:Child B onDisappear, Child A onDisappear, Parent onDisappear — 最后。与onAppear相比的相反顺序保证了正确的释放链。

何时调用.onDisappear

调用场景onDisappear取决于容器的类型。在NavigationStack中,onDisappear在pop-to-root、普通pop-back或通过滑动手势移除屏幕(interactivePopGestureRecognizer)时触发。在TabView中,切换标签页为隐藏的触发onDisappear,为显示的触发onAppear——两个修饰符几乎同时触发。

Sheet和fullScreenCover中,onDisappear在程序性dismiss(通过@Environment(\.dismiss))或向下滑动手势时调用。重要特性:如果sheet已打开但用户切换了应用程序,onDisappear在实际关闭之前不会被调用。

场景onDisappear是否调用备注
NavigationStack中的Pop动画后立即
TabView标签切换当前标签
Dismiss sheet动画完成前
List中滚动单元格超出prefetch区域
应用最小化不保证调用
Crash/watchdog kill不会被调用

.onDisappear示例

主要场景onDisappear — 资源清理、状态保存和跟踪。与onAppear不同,onDisappear任务在退出时执行,不需要检查重复,因为View只消失一次。

保存状态和取消定时器

保存状态在onDisappear中对于表单特别有用,数据在离开屏幕时必须保存。定时器和Combine订阅在onDisappear中被取消,以避免返回屏幕时发生内存泄漏。

swift
struct FormView: View {
    @State private var draftText = ""
    @State private var timer: Timer?
    
    var body: some View {
        TextField("Enter text", text: $draftText)
            .onAppear {
                timer = Timer.scheduledTimer(withTimeInterval: 60, repeats: true) { _ in
                    saveDraft()
                }
            }
            .onDisappear {
                timer?.invalidate()
                timer = nil
                saveDraft()
            }
    }
    
    private func saveDraft() {
        UserDefaults.standard.set(draftText, forKey: "draft")
    }
}

取消定时器在onDisappear中防止屏幕关闭后代码执行。如果不取消,定时器可能尝试更新不再属于当前View的@State,导致运行时警告。

跟踪屏幕上的时间

停留时长在屏幕上 — 经典的跟踪场景。我们在onAppear中记录时间,在onDisappear中计算差值,并发送带有会话持续时间的分析事件。

swift
struct TrackedView: View {
    @State private var appearTime: Date?
    
    var body: some View {
        Text("被跟踪的屏幕")
            .onAppear {
                appearTime = Date()
                Analytics.shared.logEvent("screen_view", params: ["screen": "TrackedView"])
            }
            .onDisappear {
                if let start = appearTime {
                    let duration = Date().timeIntervalSince(start)
                    Analytics.shared.logEvent("screen_close", params: [
                        "screen": "TrackedView",
                        "duration_ms": Int(duration * 1000)
                    ])
                }
            }
    }
}

.onDisappear vs .onAppear — 区别

关键区别onDisappear与onAppear — 调用顺序和触发保证。onAppear在View添加到层次结构时调用,并具有在重新创建时重复调用的特性。onDisappear在移除时调用,仅在正常关闭时保证触发,但在紧急场景下不保证。

根据WWDC 2024,Apple建议将onDisappear视为清理点,而非数据保存点。关键重要数据(支付、注册)应实时保存,而不是在View消失时,因为onDisappear在应用程序最小化时不保证调用。

特性.onAppear.onDisappear
调用时刻View添加到层次结构View从层次结构移除
顺序Parent-firstChild-first
保证紧急终止时不保证
主要任务初始化清理
重复调用View重新创建时每次消失一次

建议:仅在非关键清理和跟踪时使用onDisappear。保存数据请使用scenePhase或AppDelegate中的applicationWillTerminate通知。

典型应用场景

场景1:类似YouTube的加载。在视频详情屏幕上,onDisappear将播放位置保存到UserDefaults。重新打开时,onAppear从UserDefaults恢复位置,产生连续观看的效果。

场景2:取消Combine订阅。如果ViewModel使用Combine发布者,onDisappear通过cancellable?.cancel()取消订阅。这防止了离开屏幕后的UI更新,对于分页列表和搜索查询尤为重要。

场景3:关闭WebSocket。在具有实时连接的应用程序中(即时通讯工具、行情信息流),onDisappear关闭WebSocket连接以节省电池和流量。重新打开在返回屏幕时的onAppear中完成。

在onDisappear中关闭WebSocket

WebSocket — 一种在离开屏幕时需要关闭的典型资源。在下面的示例中,onDisappear断开socket,onAppear重新连接它,为多屏幕应用程序提供显著的流量节省。

swift
struct ChatView: View {
    @StateObject private var socket = WebSocketManager()
    
    var body: some View {
        ChatListView(messages: socket.messages)
            .onAppear {
                socket.connect()
            }
            .onDisappear {
                socket.disconnect()
            }
    }
}

重要:在TabView中切换标签页时,当前标签的onDisappear和下一标签的onAppear几乎同时触发。对于WebSocket,这可能导致disconnect-connect循环,产生额外负载。解决方案 — 使用延迟或检查下一个屏幕是否需要socket。

常见问题解答

.onDisappear可能不会被调用吗?

是的,.onDisappear在应用程序紧急终止(crash、watchdog kill)、不关闭屏幕而最小化应用程序或应用程序在后台被销毁的系统场景下不保证调用。对于关键重要数据,请使用scenePhase或applicationWillTerminate。

如何区分.onDisappear和.scenePhase?

.onDisappear在View从层次结构中被移除时触发 — 针对特定屏幕的本地回调。.scenePhase(通过@Environment(\.scenePhase))在整个应用程序状态改变时触发:active、inactive、background。跟踪屏幕时间使用onAppear+onDisappear,保存全局状态使用scenePhase。

为什么.onDisappear会连续被调用多次?

原因 — 快速切换标签页或同一屏幕的重复push/pop。SwiftUI可能创建新的View实例、删除旧的、再次创建——每次都触发onDisappear和onAppear。检查您是否使用了.id()、.equatable(),或者在父级body中是否重新创建了View。

.onDisappear在macOS上的SwiftUI中有效吗?

是的,.onDisappear在macOS(10.15+)上完全支持,行为相同:在关闭窗口、移除split view面板或dismiss模态窗口时调用。在macOS上,onDisappear不仅在关闭窗口时调用,在隐藏窗口(hide)时也会调用,这对macOS应用程序很重要。

如何在.onDisappear中取消URLSessionTask?

将对URLSessionTask的引用保存在@State中,并在onDisappear中调用task.cancel()。或者使用.task修饰符,它在View消失时自动取消async操作。对于所有async操作(包括URLSession),优先使用.task。

总结

  • .onDisappear — 与onAppear配对的修饰符,用于View消失时的资源清理和跟踪。
  • Child-first顺序 — 子View比父View更早收到onDisappear,以形成正确的释放链。
  • 不保证调用 — 在crash、最小化或系统终止时,onDisappear可能不起作用。
  • 主要场景 — 取消定时器、关闭WebSocket、保存状态、跟踪时长。
  • 分析 — onDisappear通过Date().timeIntervalSince计算dwell time发送screen_close事件。
  • 对于async操作 — 使用.task替代带Task {}的onDisappear,因为.task会自动取消请求。

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

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

讨论项目

另请阅读