Typed Error(类型化错误) 是 Swift 6.0 引入的一项重大语言特性。它允许开发者在函数声明中明确指定可能抛出的错误类型,而不仅仅是模糊的 throws。
在此之前,Swift 的所有错误在底层都会被“装箱”成 any Error(存在体类型),这类似于一种类型擦除。
1. 语法差异:从“模糊”到“精准”
普通 Error (Untyped)
在 Swift 5.9 及以前,你只能知道一个函数“会报错”,但不知道会报“什么错”。
Swift
func fetchData() throws -> Data {
// 编译器只知道这里会抛出 any Error
}
// 调用方:必须写一个 catch all 分支,或者处理 any Error
do {
try fetchData()
} catch {
// 这里的 error 类型是 any Error,需要手动转型
if let networkError = error as? NetworkError { ... }
}
Typed Error (Swift 6.0)
现在你可以通过 throws(ErrorType) 明确指明错误契约。
Swift
func fetchData() throws(NetworkError) -> Data {
// 编译器强制要求只能抛出 NetworkError 或其子类
}
// 调用方:编译器知道 error 确切就是 NetworkError
do {
try fetchData()
} catch {
// 这里的 error 自动推断为 NetworkError 类型,无需 as? 转换
switch error {
case .timeout: ...
case .noConnection: ...
}
}
2. 核心优势:为什么我们需要它?
A. 增强防御性:编译器级别的契约
在普通 throws 中,如果你在函数里不小心抛出了一个逻辑之外的错误,编译器不会拦你。
而在 Typed Error 中,如果你声明了 throws(MyError) 却尝试抛出 OtherError,编译会报错。这确保了 API 的行为与文档高度一致。
B. 性能飞跃:消除“装箱”开销
这是底层实现上的最大优势。
-
普通 Error:抛出错误时,系统需要在堆(Heap)上分配内存来包装错误对象,并处理引用计数。
-
Typed Error:由于编译器确切知道错误的大小,它可以直接通过寄存器或栈传递错误值,完全避免了动态内存分配。
在嵌入式 Swift (Embedded Swift) 等对内存极其敏感的场景下,这是唯一可用的错误处理方案。
C. 完美的泛型支持
在编写高级函数(如 map 或 flatMap)时,Typed Error 允许错误类型进行“透传”。
3. 性能与内存模型对比
| 特性 | 普通 throws | Typed throws(E) |
|---|---|---|
| 错误类型 | any Error (运行时确定) | E (编译时确定) |
| 内存分配 | 涉及堆分配 (Boxing) | 零堆分配 (Inline/Register) |
| ** catch 逻辑** | 需要 as? 类型转换 | 自动推断,直接匹配 |
| ABI 稳定性 | 适合高度抽象的库 | 适合性能敏感或严格契约的模块 |
4. 什么时候不应该使用 Typed Error?
虽然 Typed Error 看起来很棒,但它并不是要完全取代普通 throws。
-
过度耦合:如果你在公共 API 中过度使用具体的错误类型,未来如果你想增加一种新的错误类别,可能会破坏调用方的代码。
-
推荐做法:
- 使用 Typed Error:在模块内部、底层库、性能敏感的组件、或者错误类型非常固定的场景(如
Result的桥接)。 - 使用普通 Error:在复杂的业务层、大型插件系统的接口处,保持灵活性。
- 使用 Typed Error:在模块内部、底层库、性能敏感的组件、或者错误类型非常固定的场景(如
总结:防御式编程的新武器
Typed Error 让 Swift 的错误处理从“运行时检查”进化到了“编译时保证”。它结合了 Result<T, E> 的类型安全性和 throws 语法的简洁性。