开发 iOS 应用时,你迟早会遇到这种情况:一个页面混杂着加载中、成功、失败和无数据状态。
用大量 Bool 变量(如 isLoading、hasError、isEmpty)进行管理时,变量越多就越像地狱。
明明处于加载中,错误却也是 true 的状态。逻辑上说不通,但代码中却很容易产生这种组合。
先说结论:这种情况可以用一个 Swift enum 创建状态机(FSM),无需 State Pattern,让状态管理更加清晰。
今天我们结合代码,看看如何摆脱 Bool 地狱。
什么是状态机(FSM)?
不用想得太复杂。
状态机 FSM(Finite State Machine)指的是:可能存在的状态数量是有限的,并且状态只能按照既定规则变化。
想想红绿灯就容易理解了。
绿灯 → 黄灯 → 红灯。只能按这个顺序变化,不会从绿灯直接跳到红灯。
应用页面也是一样。它会从 로딩 → 성공 或 로딩 → 실패,不应该存在同时是 성공 和 로딩 的状态。
但如果用多个 Bool 变量管理,代码就可能创建不应存在的状态。
enum 正好可以阻止这种情况。
使用 Swift enum 创建状态机的方法
Swift 的 enum 不只是常量列表。
关键在于每个 case 都可以携带值(associated value)。因此,一个 enum 就能同时保存状态和数据。
我把页面状态定义如下。
enum LoadState {
case idle // 尚未执行任何操作
case loading // 加载中
case loaded([Item]) // 成功,包含数据
case failed(Error) // 失败,包含错误
}
可以看到,loaded携带项目数组,而 failed携带错误。
成功时一定有数据,失败时一定有错误。像“没有数据却成功”这样的奇怪状态根本无法创建。
在视图中,只需查看这一个状态并渲染页面即可。
switch state {
case .idle: EmptyView()
case .loading: ProgressView()
case .loaded(let items): ItemList(items)
case .failed(let error): ErrorView(error)
}
由于 switch 会强制处理所有 case,之后新增状态时,编译器会立即指出未处理的地方。
这就是很大的优势:由编译器而不是人来发现遗漏的状态。
不使用 State Pattern 也可以吗?
面向对象的教材通常会通过 State Pattern 讲解状态管理。
为每个状态创建一个类,用协议将它们组织起来,再把转换逻辑放入各个类中。
说实话,对于页面状态管理来说,这通常有些过度设计。
我简单比较了这两种方式。(基于我 2026 年的实际工作经验)
| 项目 | enum FSM | State Pattern |
|---|---|---|
| 文件/类型数量 | 一个 enum | 与状态数量相同的类 |
| 防止遗漏状态 | 强制使用 switch,由编译器检查 | 需要人工自行确认 |
| 携带数据 | 通过 associated value 自然实现 | 通过属性单独管理 |
| 适用规模 | 大约 3~7 个状态 | 每个状态的逻辑非常复杂时 |
如果状态数量适中,且各状态的逻辑不重,enum FSM 会轻量得多,也更安全。
相反,如果每个状态都包含几十行复杂行为,可以考虑 State Pattern 或拆分为独立对象。
并不存在绝对正确的一种选择。
如何管理状态转换?
你可能会问:只创建一个 enum 的话,不是可以随意切换到任何状态吗?
问得好。所以我会把转换规则集中到一个函数中。
mutating func fetch() {
guard case .idle = self else { return } // idle仅在 idle 时开始
self = .loading
}
guard case明确规定:“只有当前处于 idle 时,才能切换到加载中。”
把转换条件集中在一个地方,状态就会像红绿灯一样,只沿着既定路径流转。
这样以后重新查看代码时,也能一眼看出这个状态可以从哪里转到哪里。
问:状态超过 5 个怎么办?
enum 仍然可以很好地应对。不过当转换规则变复杂时,建议按状态将转换函数拆分成更小的函数进行整理。
问:它和 SwiftUI 适配吗?
非常适合。将 enum 状态放在 @State 或 @Published 中,再通过 switch 绘制视图,状态和页面就能紧密同步。
一开始几个 Bool 似乎就够了,但当状态开始相互纠缠,最终还是会回到 enum FSM。
如果你正为页面状态管理烦恼,不妨先试着替换成今天看到的 enum。你会发现它比想象中更快让事情变简单。

