12-5.【SwiftUI】@State、@StateObject、@ObservedObject、@EnvironmentObject 的所有权和生命周期差异

2 阅读3分钟

在 SwiftUI 中,这四个属性包装器(Property Wrappers)构成了状态管理的基石。理解它们的关键在于两个维度:谁负责创建它(所有权) 以及 它能活多久(生命周期)

我们可以把它们分为两大阵营: “生产者”“消费者”


1. 生产者阵营:我创建,我拥有

在这个阵营中,View 是数据的主人。即使 View 的 struct 被销毁重建,SwiftUI 也会在后台保护这些数据。

@State (值类型管家)

  • 所有权:View 自己拥有。
  • 生命周期:与 View 的**身份(Identity)**绑定。只要 View 在渲染树中没变,数据就一直都在。
  • 适用场景:简单的私有状态,如开关、字符串、简单结构体。
  • 防御点:必须标记为 private,防止外部干预。

@StateObject (引用类型管家)

  • 所有权:View 拥有这个实例。
  • 生命周期这是关键! 它只会被初始化一次。即使 View 重复执行 init(),SwiftUI 也会复用第一次创建的那个实例。
  • 适用场景:在当前 View 中初始化的 ViewModel 或数据管理器。

2. 消费者阵营:我使用,但不拥有

在这个阵营中,View 只是借用数据。数据由外部传入,View 仅负责监听变化并重绘。

@ObservedObject (临时引用)

  • 所有权:View 不拥有它。
  • 生命周期:随 View 的 init() 而重连。
  • 致命陷阱:如果你在 View 内部 init 一个 ObservedObject,每次父视图刷新导致当前 View 重新 init 时,你的对象都会被重置
  • 适用场景:父视图传给子视图的 ViewModel。

@EnvironmentObject (共享引用)

  • 所有权:由祖先视图拥有(通常由祖先通过 .environmentObject() 注入)。
  • 生命周期:只要注入它的那个祖先还活着,它就活着。
  • 适用场景:跨层级共享的数据,如用户信息、主题设置、全局设置。
  • 防御点:如果忘记在祖先节点注入,App 会在运行时崩溃

3. 生命周期与所有权对比表

包装器数据类型谁创建?随 View 重绘而重置?防御式建议
@State值类型 (Struct)当前 View (SwiftUI 保护)仅用于局部 UI 逻辑
@StateObject引用类型 (Class)当前 View (仅初始化一次)创建对象时必选
@ObservedObject引用类型 (Class)外部传入 (若在内部 init)传递对象时必选
@EnvironmentObject引用类型 (Class)祖先注入用于全局/深层状态

4. 黄金准则:如何选?

  1. 如果你在 View 里 init 一个类,请用 @StateObject
  2. 如果你从父视图 接收 一个类,请用 @ObservedObject
  3. 如果你需要 跨越 3 层以上 的视图传递数据,请用 @EnvironmentObject
  4. 如果是 简单的标志位 或 UI 内部状态,请用 @State

总结

记住一句话: @StateObject 是为了“创造”,而 @ObservedObject 是为了“感知”。 如果在错误的地方使用了 @ObservedObject 来初始化对象,你会发现你的数据会在 UI 刷新时莫名其妙地重置为初始状态。