在现代 iOS 开发中,Swift 的 Result
类型是处理同步与异步错误的一大利器。相比传统的 throws
/ do-catch
语法,它更清晰、结构化,也更易于组合式编程。
本文将带你从 Result
的基础定义出发,逐步深入其在实际项目中的多种应用,包括:
Result
类型结构和基本用法- 使用
Result(catching:)
简化抛出函数调用 - 使用
get()
还原抛出异常 - 和
map
/flatMap
/mapError
组合式编程 - 与
async/await
的结合 - 高阶封装与扩展函数
什么是 Result
类型?
Swift 在 5.0 引入了标准库的 Result
枚举,用于统一表示一个操作是成功还是失败:
enum Result<Success, Failure: Error> {case success(Success)case failure(Failure)
}
这种设计使得我们可以显式处理成功和失败的情况,并能将其作为值传递。
基础用法
例如我们封装一个网络请求:
enum NetworkError: Error {case invalidURLcase requestFailed
}func fetchData(from urlString: String) -> Result<Data, NetworkError> {guard let url = URL(string: urlString) else {return .failure(.invalidURL)}// 假设这里成功返回let data = Data()return .success(data)
}
调用者可以显式处理成功与失败:
switch fetchData(from: "https://example.com") {
case .success(let data):print("获取到数据:\(data.count) bytes")
case .failure(let error):print("失败:\(error)")
}
Result(catching:)
:让 throws
函数更优雅
传统写法:
do {let value = try someThrowingFunction()print(value)
} catch {print(error)
}
改写为:
let result = Result { try someThrowingFunction() }
这等价于:
let result: Result<ReturnType, Error>
do {let value = try someThrowingFunction()result = .success(value)
} catch {result = .failure(error)
}
这是 Swift 标准库为 Result 提供的一个便捷初始化方式:
public init(catching body: () throws -> Success)
配合 switch
或 .get()
解包:
do {let value = try result.get()print(value)
} catch {print(error)
}
这段代码的重点在于:result 是一个 Result<Success, Failure>
类型,你通过 .get()
方法来从中提取值。
.get() 是 Result 提供的一个方法:
func get() throws -> Success
• 如果是 .success(value),就返回 value
• 如果是 .failure(error),就会 throw error
也就是说,它把 Result 恢复成一个“会抛出错误”的函数调用。
它等价于这样写:
switch result {
case .success(let value):print(value)
case .failure(let error):print(error)
}
或者:
if case let .success(value) = result {print(value)
} else if case let .failure(error) = result {print(error)
}
.get() 允许你在使用 Result 的同时,兼容 throws 风格的控制流。这在以下场景特别有用:
- 在函数中继续“throw”出去:
func loadUser() throws -> User {let result = Result { try JSONDecoder().decode(User.self, from: data) }return try result.get() // 自动把错误 throw 出去
}
不想写 switch,只想“顺着抛出去”,那 .get() 就特别方便。
- 在某些 try-catch 中临时转回 throws 风格:
do {let user = try result.get()print("用户名:", user.name)
} catch {print("解包失败:", error)
}
这段代码可读性也很好,就像你调用了一个 throws 函数一样。
函数组合:map / flatMap / mapError
map
仅在 .success
情况下转换结果:
let result: Result<Int, Error> = .success(2)
let squared = result.map { $0 * $0 } // .success(4)
flatMap
链接另一个 Result
:
func square(_ input: Int) -> Result<Int, Error> {return .success(input * input)
}let chained = result.flatMap(square) // .success(4)
mapError
将错误转换成另一种类型:
let errorMapped = result.mapError { error inreturn MyCustomError(reason: error.localizedDescription)
}
与 async/await 协作
Swift 的 async/await 与 throws 通常这样使用:
func loadRemoteData() async throws -> Data
这意味着你在调用它时必须用 try await
,并包裹在 do-catch
中:
do {let data = try await loadRemoteData()
} catch {// 处理错误
}
你可以把这段异步 throws 的代码包装为 Result<Success, Failure> 类型:
let result = await Result {try await loadRemoteData()
}
这是一种将“异步抛错逻辑”变成一个明确的结果值的方式,使得你可以把逻辑结构化、组合、传递。
实用场景:
在 ViewModel 中捕获异步错误
目的:
• 避免在 SwiftUI 的 @MainActor、@Published 环境中嵌套 do-catch
• 让错误与成功的结果都作为值进行统一处理
示例:
@MainActor
func fetchData() async {let result = await Result {try await apiService.loadUser()}switch result {case .success(let user):self.user = usercase .failure(let error):self.errorMessage = error.localizedDescription}
}
这样可以:
• 更易测试:你可以直接构造 .success / .failure 进行单元测试
• 更可组合:可以继续 .map() 或 .mapError() 处理后续逻辑
搭配 .mapError 统一错误类型
很多时候我们不希望把错误直接暴露给 UI,而是将其转为通用的 AppError:
let result = await Result {try await apiService.loadUser()
}
.mapError { error inreturn AppError.network(error.localizedDescription)
}
这让你的 ViewModel 不用关心底层的 error 是 URLError 还是 DecodingError,而是统一成业务可识别的类型。
避免多个 await 重复 do-catch 块
当你在一个异步流程中连续调用多个 async throws 函数时,传统写法会很冗长:
do {let user = try await fetchUser()let profile = try await fetchProfile(for: user.id)let avatar = try await fetchAvatar(for: profile.avatarID)
} catch {// 各种错误可能混在一起
}
如果改用 Result 方式,每步都可以安全处理错误并组合:
let result = await Result {try await fetchUser()
}
.flatMap { user inawait Result { try await fetchProfile(for: user.id) }
}
.flatMap { profile inawait Result { try await fetchAvatar(for: profile.avatarID) }
}
再用 .get()
解包或 .switch
判断结果。
好处:
• 每一步都能捕获错误
• 易于插入中间处理(日志、缓存、fallback)
• 错误处理更细粒度、可组合
async throws 函数封装
func taskResult<T>(_ work: @escaping () async throws -> T) async -> Result<T, Error> {await Result {try await work()}
}
然后统一调用:
let result = await taskResult {try await fetchData()
}
实用扩展函数
onSuccess / onFailure
extension Result {func onSuccess(_ handler: (Success) -> Void) -> Self {if case let .success(value) = self {handler(value)}return self}func onFailure(_ handler: (Failure) -> Void) -> Self {if case let .failure(error) = self {handler(error)}return self}
}
用法:
result.onSuccess { print("成功结果:\($0)") }.onFailure { print("错误:\($0)") }
recover:提供默认值或兜底方案
extension Result {func recover(_ value: @autoclosure () -> Success) -> Success {switch self {case .success(let success): return successcase .failure: return value()}}
}
实战案例:网络请求封装
func loadUser() async -> Result<User, NetworkError> {await Result {let (data, _) = try await URLSession.shared.data(from: URL(string: "https://api.example.com/user")!)return try JSONDecoder().decode(User.self, from: data)}.mapError { error in.requestFailed}
}
总结对比表
特性 | throws/do-catch | Result |
---|---|---|
返回结构 | 无返回值 | 显式 success/failure |
链式调用 | 不方便 | 支持 map / flatMap |
多阶段处理 | 需嵌套 | 可组合 |
可读性 | 中 | 高 |
可测试性 | 异常难模拟 | 易构造成功/失败场景 |
参考资源
- Apple 官方文档 - Result
结语
Swift 的 Result
是将函数式编程与错误处理融合的优秀设计。
它不仅简化了异常流程,还增强了组合能力。我们应在业务逻辑、网络请求、异步流程、状态传递中更多地使用 Result
,写出更简洁、更安全的代码。
让我们从现在开始,用 Result
统一错误处理思路,让代码更具表达力!
最后,希望能够帮助到有需要的朋友,如果觉得有帮助,还望点个赞,添加个关注,笔者也会不断地努力,写出更多更好用的文章。