在現代 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
統一錯誤處理思路,讓代碼更具表達力!
最后,希望能夠幫助到有需要的朋友,如果覺得有幫助,還望點個贊,添加個關注,筆者也會不斷地努力,寫出更多更好用的文章。