软件设计

使用 Swift enum 创建状态机(FSM):无需 State Pattern 管理状态

开发 iOS 应用时,你迟早会遇到这种情况:一个页面混杂着加载中、成功、失败和无数据状态。

4 分钟阅读
使用 Swift enum 创建状态机(FSM):无需 State Pattern 管理状态 封面图

开发 iOS 应用时,你迟早会遇到这种情况:一个页面混杂着加载中、成功、失败和无数据状态。

用大量 Bool 变量(如 isLoadinghasErrorisEmpty)进行管理时,变量越多就越像地狱。

明明处于加载中,错误却也是 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。你会发现它比想象中更快让事情变简单。

延伸阅读