在响应式框架中,错误处理的优雅程度取决于你如何将“异常”转化为“状态”。核心目标是将不可控的 Error 转换为可预测的 Action 或 View State。
以下是针对三个主流框架的防御式设计与测试策略:
1. Combine: 强类型流与错误转换
Combine 的 Publisher 要求定义 Failure 类型。优雅处理的关键在于尽早将 Error 映射为业务枚举。
策略:使用 mapError 和 catch
- 防御点:不要让底层的
URLError渗透到 UI 层。使用mapError进行类型收窄。 - 优雅恢复:使用
catch在发生错误时切换到备用流(例如从缓存读取数据),保证流不会因错误而彻底终止。
Swift
func fetchData() -> AnyPublisher<Data, AppError> {
URLSession.shared.dataTaskPublisher(for: url)
.map(.data)
.mapError { AppError.network($0) } // 统一错误类型
.catch { error in
// 如果报错,尝试从本地磁盘恢复
return self.localCache.publisher()
}
.eraseToAnyPublisher()
}
2. SwiftUI: 错误状态驱动的 UI
SwiftUI 的核心是状态驱动。处理错误的最佳实践是使用一个枚举状态机,而不是散乱的布尔值。
策略:枚举状态机
- 防御点:避免同时出现
isLoading = true且error != nil的矛盾状态。 - 可测试性:由于状态是确定的枚举,你可以通过简单的单元测试验证 UI 逻辑。
Swift
enum LoadingState<Value, Error: LocalizedError> {
case idle
case loading
case success(Value)
case failure(Error)
}
struct DataView: View {
@State var state: LoadingState<User, AppError> = .idle
var body: some View {
switch state {
case .loading: ProgressView()
case .success(let user): Text(user.name)
case .failure(let error):
VStack {
Text(error.errorDescription ?? "未知错误")
Button("重试") { retryTask() } // 支持恢复
}
default: EmptyView()
}
}
}
3. TCA (The Composable Architecture): 纯函数式错误处理
TCA 将错误处理提升到了架构级的高度。错误被视为一种 Action。
策略:catchToEffect 与依赖注入
- 防御点:所有的副作用(如 API 调用)都封装在
Dependency中。 - 可测试恢复:TCA 的
TestStore允许你模拟失败的Effect,并验证 Reducer 是否正确处理了错误 Action 并更新了状态。
Swift
// Reducer 内部
case .fetchResponse(.failure(let error)):
state.isLoading = false
state.alert = AlertState(title: { TextState(error.localizedDescription) })
return .none
case .retryButtonTapped:
return .send(.fetchRequest) // 逻辑清晰的恢复触发
4. 可测试恢复(Testable Recovery)的最佳实践
无论使用哪个框架,支持可测试恢复的共同点是依赖抽象。
A. 模拟(Mocking)错误
不要在测试中真的断开网线。通过协议(Protocol)注入 Mock Service,使其返回 .failure。
Swift
struct MockService: SearchServiceProtocol {
var errorToThrow: AppError?
func search() async throws -> [Item] {
if let error = errorToThrow { throw error }
return [Item.mock]
}
}
B. 验证恢复逻辑
在测试用例中:
- 注入会失败的 Mock。
- 触发操作,验证
State变为.failure。 - 动态更换 Mock 为成功状态(或修改其行为)。
- 触发“重试” Action,验证
State最终变为.success。
总结:防御式处理清单
| 框架 | 核心工具 | 恢复手段 |
|---|---|---|
| Combine | catch, retry(n) | 切换 Publisher |
| SwiftUI | enum State | 状态触发重绘 |
| TCA | Effect.run, AlertState | 发送重试 Action |
设计建议:始终为你的错误视图提供一个显式的“重试”路径。这不仅提升了用户体验,也迫使你在架构层考虑如何通过重新订阅流或重新触发 Action 来恢复状态一致性。