告别 UIKit 生命周期:SwiftUI 视图一生全解析——从 init 到 deinit 的“隐秘角落”

为什么 6 年后还要再聊 SwiftUI 生命周期?

2019 年 SwiftUI 发布时,我们像"翻译官"一样,把 UIKit 的 viewDidLoadviewWillAppear 强行映射到新框架。

如今苹果已围绕 SwiftUI 重新设计了应用级生命周期(AppSceneWindowGroup),UIKit 反而成了"遗留项目"。

再啃一遍纯 SwiftUI 视角的视图生命周期,已是刚需,而非可选项。

视图一生的"时间轴"总览

阶段 触发时机 同步/异步 典型用途
init 结构体创建 同步 初始化常量、属性默认值
task 初始化后、appear 前 异步 网络请求、数据库操作、初始化重量级 Observable
onAppear 已渲染到屏幕 同步 滚动定位、几何计算、埋点统计
状态变更 任意时刻 同步 @State / @Binding 驱动 body 重新计算
onDisappear 即将离开屏幕 同步 停止定时器、手动取消网络任务
Observable 的 deinit 引用计数=0(实例销毁时) 同步 "最后遗言":写日志、发通知(使用场景较少)

逐阶段深度拆解

  1. init ------ 结构体的"出生证"
swift 复制代码
struct DetailView: View {
    let postID: Int
    @State private var likes: Int
    
    // 自定义初始化:可以注入常量、设置默认值
    init(postID: Int) {
        self.postID = postID
        _likes = State(wrappedValue: 0) // 手动包裹 State
        print("【init】postID=\(postID)")
    }
    
    var body: some View { EmptyView() }
}

注意

  • View 是 struct,没有真正的"析构",init 可能被频繁调用(body 评估时)。
  • 只放轻量级代码;重量级对象请放到 task@StateObject 里。
  1. task ------ 苹果推荐的"异步入口"
swift 复制代码
var body: some View {
    Text("Hello")
        .task {              // ✅ 异步闭包,自动管理生命周期
            await loadData()
        }
        .task(priority: .userInitiated) { // 可指定优先级
            await loadImage()
        }
}

特性

  • 在 appear 之前、init 之后 执行。
  • 与视图生命周期强绑定:视图消失时,协程自动取消(Task.isCancelled = true)。
  • 苹果官方推荐:把重量级 Observable 初始化放这里,避免阻塞主线程。
  1. onAppear ------ 屏幕亮起的"哨兵"
swift 复制代码
ScrollView {
    LazyVStack {
        ForEach(items) { item in
            RowView(item)
                .onAppear {
                    // 滚动到指定 index
                    if item.id == targetID {
                        proxy.scrollTo(item.id, anchor: .center)
                    }
                }
        }
    }
}

适用场景

  • 需要几何信息已就绪(GeometryReader 已布局)。
  • 埋点、Analytics、页面追踪。
  • 不能执行长时间阻塞任务,否则卡 UI。
  1. 状态与数据流 ------ 视图"心跳"的发动机
swift 复制代码
struct Counter: View {
    @State private var count = 0          // 局部
    @Binding var globalTitle: String      // 父级
    @Environment(\.colorScheme) var scheme // 系统
    
    var body: some View {
        Button("+\(count)") {
            count += 1
            globalTitle = "已点 \(count) 次"
        }
        .foregroundColor(scheme == .dark ? .yellow : .black)
    }
}

规则

  • 任何 @State / @Binding / @Observable 变更都会触发 body 重新求值。
  • SwiftUI 采用细粒度 diff,只刷新变化的子树。
  • 不要在 body 里产生副作用(网络、文件 IO),副作用放到 task / onAppear 里。
  1. onDisappear ------ 离开舞台的"幕布"
swift 复制代码
struct PlayerView: View {
    @StateObject private var player = VideoPlayer()
    
    var body: some View {
        VideoPlayerView()
            .onDisappear {
                player.pause()        // 手动暂停
                saveWatchProgress()   // 持久化进度
            }
    }
}

注意

  • 不保证视图被销毁(SwiftUI 可能缓存)。
  • 若需要真正释放资源,请把清理逻辑放到 ObservableObjectdeinit
  1. Observable 的 deinit ------ 结构体没有"遗言",但类有
swift 复制代码
final class VideoModel: ObservableObject {
    @Published var progress: Double = 0
    
    deinit {
        // 最后广播
        NotificationCenter.default.post(name: .playerDead, object: progress)
        print("【deinit】最后进度=\(progress)")
    }
}

使用场景

  • 必须在对象释放前发送消息、写日志、刷磁盘。
  • 因为 View 是 struct,没有 deinit,所以把"遗言"托管给引用类型。

完整示例:一个"会呼吸"的详情页

swift 复制代码
@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            DetailView1(postID: 42)
        }
    }
}

struct DetailView1: View {
    let postID: Int
    @State private var uiModel: Post? = nil
    @State private var isLoading = true
    
    // 1. 初始化
    init(postID: Int) {
        self.postID = postID
        print("【init】")
    }
    
    var body: some View {
        Group {
            if isLoading {
                ProgressView("加载中...")
            } else if let data = uiModel {
                ScrollView {
                    VStack(alignment: .leading) {
                        Text(data.title).font(.title)
                        Text(data.body)
                    }
                    .padding()
                }
            }
        }
        .task {                       // 2. 异步拉数据
            print("【task】")
            await fetchPost()
        }
        .onAppear {                   // 3. 埋点
            print("【onAppear】")
        }
        .onDisappear {                // 4. 清理
            print("【onDisappear】")
            URLSession.shared.invalidateAndCancel()
        }
    }
    
    func fetchPost() async {
        guard let url = URL(string: "https://jsonplaceholder.typicode.com/posts/\(postID)") else { return }
        do {
            let (data, _) = try await URLSession.shared.data(from: url)
            uiModel = try JSONDecoder().decode(Post.self, from: data)
            isLoading = false
        } catch {
            // 简化错误处理
            isLoading = false
        }
    }
}

struct Post: Decodable {
    let title: String
    let body: String
}

运行日志

csharp 复制代码
【init】
【onAppear】
【task】

常见陷阱 & 调试技巧

  1. init 被反复调用

    在 body 里 print("init") 会发现日志刷屏 → 说明你把重逻辑放进了 init,应迁移到 task

  2. onDisappear 不调用

    视图被嵌在 TabViewNavigationStack 里,可能被缓存;依赖 onDisappear 做"必须释放"逻辑时要谨慎。

  3. task 不会重复执行

    同一视图实例复用时,task 只跑第一次;若需要"每次出现都拉新数据",用 .id(uuid) 强制重建视图,或监听 onAppear

扩展场景:把生命周期变成"业务事件"

  1. 预加载 + 缓存

    task 里拉取下一页数据,用 NSCache 缓存,用户滑到下一页时零等待。

  2. 权限申请

    相机、相册权限首次弹窗放在 onAppear,用户拒绝后下次进入再次引导。

  3. 屏幕旋转适配

    onAppear 记录当前 UIWindowScene.interfaceOrientation,旋转后对比,决定是否重建布局。

  4. SwiftData 自动保存

    @Modelsave() 放在 onDisappear,用户离开页面前落盘,比 scenePhase 更细粒度。

写在最后

6 年前我们拿 UIKit 的尺子量 SwiftUI,今天已经可以把尺子扔掉------SwiftUI 给了我们更少模板、更直观、更声明式的生命周期,

但也要求我们更自律:"轻 init、重 task、慎清理、勤递归最小状态。"

当你不再追问"viewDidLoad 在哪",而是自然而然把网络请求塞进 task, 把清理逻辑交给 onDisappear, 就真正从UIKit 移民变成了SwiftUI 原住民。

愿我们都能写出"呼吸感"十足的代码:该出现时出现,该消失时消失,该干活时绝不卡屏。

学习参考

  1. captainswiftui.substack.com/p/the-simpl...
相关推荐
HarderCoder5 小时前
Swift 中的基本运算符:从加减乘除到逻辑与或非
ios·swift
HarderCoder5 小时前
Swift 中“特性开关”实战笔记——用编译条件+EnvironmentValues优雅管理Debug/TestFlight/AppStore三环境
ios·swift
HarderCoder5 小时前
Swift 并发任务中到底该不该用 `[weak self]`?—— 从原理到实战一次讲透
ios·swift
大熊猫侯佩10 小时前
天网代码反击战:Swift 三元运算符的 “一行破局” 指南
swiftui·swift·apple
大熊猫侯佩1 天前
在肖申克监狱玩转 iOS 26:安迪的 Liquid Glass 复仇计划
ios·swiftui·swift
大熊猫侯佩1 天前
用最简单的方式让 SwiftUI 画一颗爱你的小红心
swiftui·swift·apple
HarderCoder2 天前
Swift 初探:从变量到并发,一文带你零基础读懂官方 Tour
swift