返回文章列表

SwiftUI 系列 04|状态管理怎么理解:@State、@Binding、@ObservedObject、@StateObject 怎么选?

真正关键的不是背语法,而是先回答“这份数据归谁拥有、由谁驱动界面变化”

SwiftUI 真正最容易让人“卡住”的地方,不是布局,而是状态。

最常见的问题通常是这些:

  • 这个值到底该用 @State 还是 @Binding
  • 为什么 ViewModel 会重复初始化
  • 子视图为什么改不到父视图数据
  • 为什么值明明变了,页面却没按预期刷新

这些问题如果只靠记忆属性包装器,很容易越记越乱。
因为真正的根源通常不是 API 记不住,而是:

你没有先把“这份状态到底归谁拥有”这件事想清楚。

一、先别急着选包装器,先问:这份数据是谁的

这是我觉得最重要的问题。

在 SwiftUI 里,每次看到一个状态值,都先问自己:

  • 这个值是当前 View 自己拥有的吗
  • 还是父视图拥有、子视图只是借来改
  • 还是某个外部对象在拥有,当前 View 只是观察

这个问题一旦答清楚,很多包装器的选择会自然很多。
相反,如果跳过这一步,直接问“这个场景常用哪个”,就很容易靠试错堆代码。

二、@State 真正适合的是“当前 View 自己的本地状态”

@State 最适合的,不是“所有会变的值”,而是:

这份状态只属于当前 View,本地存在、本地消费、离开这个 View 基本就没意义。

例如:

  • 输入框当前内容
  • sheet 是否展示
  • 某个局部展开折叠状态
  • 当前 tab 选择

这些状态的共同点是:

  • 归属明确
  • 生命周期和这个 View 强绑定
  • 不值得上升成外部业务状态

如果一份状态离开当前 View 就不太有意义,那它通常很适合 @State

三、@Binding 的重点不是“能传值”,而是“所有权没变,只是把写权限借出去”

很多人会把 @Binding 理解成“子视图也能改父视图值”。
这个理解不算错,但不够精确。

更重要的是:

  • 这份状态仍然属于父视图
  • 子视图没有拥有它
  • 子视图只是拿到了读写通道

这件事很关键,因为它决定了你在组件抽分时是否还保留了正确的数据归属。

也就是说:

  • @State 是“我自己的”
  • @Binding 是“还是你的,但我被授权可以改”

一旦按所有权来理解,@Binding 就不会再被误当成“数据共享万能方案”。

四、@ObservedObject@StateObject 最容易混,是因为大家常常把“观察”和“拥有”混在一起

这两个包装器最常见的误用,本质上都来自于一个问题:

没有分清“当前 View 是观察一个外部对象”,还是“当前 View 自己创建并稳定持有这个对象”。

@ObservedObject

更适合表示:

  • 这个对象是外部传进来的
  • 当前 View 只是观察它
  • 当前 View 不负责创建,也不负责稳定持有它

@StateObject

更适合表示:

  • 当前 View 自己创建这个对象
  • 并且希望在 View 刷新过程中稳定持有它

这两个的根本区别,不在“哪个更高级”,而在:

  • 谁创建了对象
  • 谁负责对象生命周期

这就是为什么很多 ViewModel 重复初始化问题,本质上不是 SwiftUI “玄学”,而是对象所有权放错了位置。

五、一个非常实用的工程判断:本地 UI 状态和业务状态要分开想

很多页面状态会混乱,不是因为包装器太多,而是你把不同层级的状态揉成了一团。

例如一个页面里可能同时有:

  • 输入框内容
  • 筛选弹窗是否打开
  • 网络请求结果
  • 页面 loading 状态
  • 业务对象列表

这些值看起来都叫“状态”,但它们不是同一类状态。

我通常会先粗分成两类:

1. 本地 UI 状态

例如:

  • 是否展开
  • 当前输入内容
  • 当前选中项
  • 局部展示开关

这类状态更接近 @State / @Binding

2. 业务数据状态

例如:

  • 用户信息
  • 列表数据
  • 加载流程
  • 请求错误

这类状态通常更适合放在外部可观察对象或更高层状态容器里。

一旦这两类状态不分,页面就很容易又有本地状态、又有 ViewModel 状态、又有父层传值,最后谁拥有谁都说不清。

六、很多“值变了但界面不对”的问题,本质上不是刷新失败,而是状态边界本来就混了

很多人会觉得:

  • 我明明改值了,为什么页面没像我想的那样变

这当然有时是包装器用错,但更常见的情况是:

  • 本该是父层拥有的状态,被子层临时持有了
  • 本该长期存在的对象,被当成临时观察对象处理了
  • 本地 UI 状态和业务状态互相覆盖

也就是说,看起来像“刷新问题”,本质往往还是所有权问题。

SwiftUI 的很多状态问题,一旦你沿着“谁拥有这份数据”去查,通常会比盯着界面现象更快找到根源。

七、一个我很常用的简化判断法

虽然真实项目会更复杂,但日常开发里我经常先用这套简单判断:

1. 这是当前 View 自己的简单状态吗

是,优先想 @State

2. 这是父视图拥有、当前子视图只是借来改的吗

是,优先想 @Binding

3. 这是外部传进来的可观察对象吗

是,优先想 @ObservedObject

4. 这是当前 View 自己创建并希望稳定持有的对象吗

是,优先想 @StateObject

这套判断当然不覆盖全部细节,但对于大量业务页面已经足够实用。

八、结论:状态管理真正要先搞清楚的,不是语法,而是所有权

如果只用一句话总结,我会说:

在 SwiftUI 里,状态管理最重要的不是先问“这个包装器怎么用”,而是先问“这份数据归谁拥有、由谁负责让界面随着它变化”。

一旦所有权先清楚,很多包装器选择都会自然很多。
反过来,如果所有权没想清楚,后面再熟悉 API,也很容易写出“能跑但越来越乱”的页面。