关注【索引目录】服务号,更多精彩内容等你来探索!
什么是类型化和非类型化抛出?
无类型抛出(传统 Swift)
-
函数抛出符合 Error协议的错误 any Error编译时擦除的实际错误类型 -
Catch 块必须处理所有可能的错误或使用通用的 catch -
自 Swift 2.0 以来一直是标准
类型化抛出 (Swift 6)
-
函数明确声明它们抛出的错误类型 -
关于错误类型的编译时保证 -
启用详尽的错误处理,无需默认捕获 -
提高 API 清晰度和类型安全性
技术实施细节
语法比较
无类型抛出(经典方法):
func fetchUser(id: String) throws -> User {
// Can throw any Error type
}
类型化抛出(Swift 6):
func fetchUser(id: String) throws(NetworkError) -> User {
// Can only throw NetworkError types
}
实施要点
1. 错误类型声明
-
将错误类型放在 throws关键字后的括号中 -
错误类型必须符合 Error协议 -
每个函数签名单一错误类型
2. 编译器强制执行
-
只能抛出指定的错误类型 -
尝试抛出不同的错误类型会导致编译错误 -
通过调用链保留的类型信息
3. 多种错误类型
-
对于多种错误场景使用具有关联值的枚举 -
或者,创建错误协议层次结构 -
Swift 6 不支持联合类型,例如 throws(NetworkError | ValidationError)
类型擦除:无类型抛出的隐藏成本
理解错误处理中的类型擦除
无类型抛出 - 运行时类型擦除:
-
当函数使用 plain 时 throws,Swift 会删除具体的错误类型 -
将错误装入存在容器中 any Error -
与错误值一起存储的运行时类型信息 -
错误处理需要动态调度
类型抛出 - 零类型擦除:
-
编译时保留的具体错误类型 -
无需存在容器 -
无需间接的直接内存布局 -
静态调度错误处理
内存和性能影响
// Untyped throws - requires type erasure
func fetchDataUntyped() throws -> Data {
throw NetworkError.timeout // Boxed as 'any Error'
}
// Typed throws - no type erasure
func fetchDataTyped() throws(NetworkError) -> Data {
throw NetworkError.timeout // Direct NetworkError type
}
// Memory layout comparison:
// Untyped: [Existential Container] -> [Type Metadata] -> [Error Value]
// Typed: [Error Value] (direct)
类型擦除开销细分
1. 现有容器成本
-
错误的堆分配
2. 运行时类型检查
-
catch 块中的动态强制转换
3. 优化障碍
-
编译器无法内联错误处理路径 -
错误边界上不存在持续传播
实际示例:身份验证服务
定义类型错误
enum AuthError: Error {
case invalidCredentials
case sessionExpired
case networkFailure(Int)
case twoFactorRequired
}
struct AuthToken {
let token: String
let expiresAt: Date
}
紧凑服务实现
class AuthService {
func login(email: String, password: String) throws(AuthError) -> AuthToken {
// Validate inputs
guard isValidEmail(email), !password.isEmpty else {
throw AuthError.invalidCredentials
}
// Simulate API call
let response = mockAPICall(email: email, password: password)
switch response.status {
case 200:
return AuthToken(token: response.token, expiresAt: .distantFuture)
case 401:
throw AuthError.invalidCredentials
case 403:
throw AuthError.twoFactorRequired
case 440:
throw AuthError.sessionExpired
default:
throw AuthError.networkFailure(response.status)
}
}
private func isValidEmail(_ email: String) -> Bool {
email.contains("@") && email.contains(".")
}
private func mockAPICall(email: String, password: String) -> (status: Int, token: String) {
// Simplified mock response
return (status: 200, token: "mock_token_12345")
}
}
干净的错误处理
class LoginViewModel {
private let authService = AuthService()
func performLogin(email: String, password: String) {
do {
let token = try authService.login(email: email, password: password)
storeToken(token)
navigateToHome()
} catch .invalidCredentials {
showAlert("Invalid email or password")
} catch .sessionExpired {
showAlert("Session expired. Please login again")
} catch .twoFactorRequired {
navigateToTwoFactor()
} catch .networkFailure(let code) {
showAlert("Network error: \(code)")
}
// Exhaustive - no default catch needed!
}
}
主要优势和用例
类型化抛出的好处
1. 编译时安全
-
详尽的错误处理,无需默认捕获 -
防止意外忽略特定的错误情况 -
错误类型改变时重构安全性
2. 自文档 API
-
函数签名中可见的错误类型 -
无需深入研究实施 -
调用者和实现者之间有明确的契约
3.性能优化
-
无类型擦除开销 -
直接错误类型调度 -
由于通用代码减少,二进制文件大小更小
4.更好的IDE支持
-
特定错误情况的自动完成 -
错误类型的内联文档 -
快速导航至错误定义
理想用例
1. 特定领域的库
-
具有已定义错误状态的网络客户端 -
具有特定故障模式的数据库操作 -
具有已知错误情况的文件系统操作
2. 内部模块边界
-
服务层到表示层的通信 -
存储库模式实现 -
用例/交互器错误传播
3. 公共SDK开发
-
为 SDK 使用者提供清晰的错误契约 -
版本稳定的错误处理 -
通过明确的错误减少支持负担
最佳实践
1. 错误粒度
-
过多和过少错误情况之间的平衡 -
按逻辑对相关错误进行分组 -
考虑客户需求而非实施细节
2. 误差演化
-
使用枚举来表示封闭的错误集 -
谨慎添加案例以避免破坏性变更
3.测试策略
-
为每个错误情况编写测试 -
使用类型化抛出来确保测试覆盖率
性能考虑
运行时影响
-
类型化抛出消除了类型擦除的开销 -
直接调度而不是协议见证表 -
减少错误装箱的分配
二进制大小
-
用于错误处理的较小代码生成 -
减少通用实例 -
更高效的错误传播路径
结论
Swift 6 中的类型化抛出 (Typed throws) 代表了错误处理方面的重大革新,使 Swift 更接近真正类型安全的系统编程。该功能增强了 API 的清晰度,提升了性能,并提供了编译时保证,从而减少了运行时错误。
关注【索引目录】服务号,更多精彩内容等你来探索!

