Swift UI - 谈生命周期

SwiftUI 中,视图的生命周期与传统 UIKit的显式生命周期(如 viewDidLoad, viewDidAppear)有本质区别

SwiftUI 采用声明式编程范式,视图的生命周期由框架自动管理,开发者主要通过响应状态变化和特定修饰符来参与。

一、核心生命周期阶段

1.视图创建(Initialization)

视图结构体(struct)被初始化,但此时尚未加载到视图树

注意:避免在初始化器中执行耗时操作,因为 SwiftUI 可能频繁重建视图

2.视图渲染(Rendering)

当视图首次显示依赖的 @State@ObservedObject 等状态变化时,SwiftUI 调用 body 属性计算视图描述。

视图是值类型:每次状态变化都会生成新的视图结构体(高效轻量,非实际视图销毁)。

3.视图挂载(Appearing) → onAppear
Text("Hello")
  .onAppear {
    // 视图加载到屏幕时触发(类似 viewDidAppear)
    // 常用场景:加载数据、开启监听
  }
4.视图卸载(Disappearing) → onDisappear
Text("Hello")
  .onDisappear {
    // 视图从屏幕移除时触发(类似 viewDidDisappear)
    // 常用场景:取消网络请求、清理资源
  }

二、状态驱动更新

1.视图是短暂的

SwiftUI 可能频繁销毁和重建视图结构体(非底层渲染视图)。避免依赖初始化器执行关键逻辑,改用 onAppear

2.状态管理决定生命周期
  • @State:视图私有状态,生命周期与视图树一致。
  • @ObservedObject:外部可观察对象,其生命周期由引用计数管理。
  • @StateObject:推荐替代 @ObservedObject,确保对象生命周期与视图绑定(iOS 13+)。
3.高效差异化更新

SwiftUI 通过比较视图结构体的值,智能更新实际渲染内容(非整体重建)。

三、高级控制

1. task 修饰符(iOS 15+)

在 SwiftUI 中,task 修饰符(iOS 15+)是管理异步任务生命周期的革命性工具,它通过结构化并发自动处理任务的创建、取消和清理。

task 修饰符自动将异步任务绑定到视图的生命周期:

struct UserView: View {
    @State private var userData: User?
    
    var body: some View {
        VStack {
            if let userData {
                Text(userData.name)
            }
        }
        .task { // 自动生命周期管理
            do {
                // 1. 视图出现时启动任务
                userData = try await fetchUserData()
            } catch {
                print("请求失败:", error)
            }
            // 2. 视图消失时自动取消任务
        }
    }
    
    func fetchUserData() async throws -> User {
        try await Task.sleep(for: .seconds(2)) // 模拟网络请求
        return User(name: "John Doe")
    }
}

task四大自动管理特性:

    1. 自动启动
      当视图首次出现(onAppear)时,task 自动执行异步闭包:
.task {
    // 视图出现时自动执行
    await loadData()
}
    1. 自动取消
      当视图消失(onDisappear)时,自动取消任务并传播取消信号:
.task {
    do {
        // 如果视图在请求完成前消失,此请求会被自动取消
        let data = try await URLSession.shared.data(from: url)
    } catch {
        // 捕获 CancellationError(任务被取消时抛出)
        if error is CancellationError {
            print("任务已取消")
        }
    }
}
    1. 依赖驱动的重启
      通过 id 参数响应依赖变化,自动取消旧任务并启动新任务:
.task(id: userId) { // 当 userId 变化时
    await fetchUserProfile(userId: userId)
}

/---------------/
✅ userId 变化 → 取消当前任务 → 启动新任务
❌ 没有 id 参数 → 依赖变化时任务继续运行(可能数据过时)
    1. 子任务自动传播
      任务内部的子任务会自动继承取消状态:
.task {
    // 父任务
    async let image = loadProfileImage() // 子任务1
    async let bio = loadUserBio()       // 子任务2
    
    // 视图消失时,所有子任务自动取消
    let (finalImage, finalBio) = await (image, bio)
}

自动取消传播机制:
1.当父任务取消时,所有子任务自动收到取消信号;
2.正在执行的 await 点抛出 CancellationError;
3.所有任务层级自动清理资源。

正确实践:

.task {
    // 1. 使用支持取消的API
    let (data, _) = try await URLSession.shared.data(for: request)
    
    // 2. 每次await后检查取消状态
    try Task.checkCancellation()
    
    // 3. 自动在主线程更新UI
    self.data = data
}

面试点睛:强调 task 如何解决异步编程的三大痛点:
1.资源泄漏(忘记取消任务)
2.状态爆炸(更新已销毁视图)
3.竞态条件(新旧任务冲突)

2. id 修饰符强制刷新

通过改变视图标识重置生命周期:

ChildView()
  .id(UUID()) // 每次标识变化时完全重建

可理解为:释放旧的,创建新的。

四、总结

遵循 SwiftUI 的声明式思维:关注状态变化而非生命周期事件,用 onAppear/onDisappear 处理副作用,用 @StateObject 管理对象生命周期。

Swift UI 与 Swift相比较

完整生命周期流程示例:

struct LifecycleDemo: View {
  @State private var isActive = false
  
  var body: some View {
    VStack {
      Toggle("显示子视图", isOn: $isActive)
      
      if isActive {
        ChildView()
          .onAppear { print("子视图显示") }
          .onDisappear { print("子视图移除") }
      }
    }
  }
}

struct ChildView: View {
  init() { print("子视图初始化(可能多次调用)") }
  var body: some View { Text("子视图内容") }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。