在Swift 5 之前喉祭,拋出和處理錯誤的標準做法是使用 throws
try
catch
, 異步錯誤使用的是 completion: @escaping (ResponseType?, ErrorType?) -> Void
的形式進行回調(diào)爆办。 然而一些第三方庫已經(jīng)發(fā)現(xiàn)了缺乏一個泛型 Result<Success,Failure>
類型的不方便难咕,紛紛實現(xiàn)了自己的 Result 類型以及相關的 Monad 和Functor 特性。
Swift 5 盡管仍正在開發(fā)中距辆,我們看到 Result<Success, Failure>
類型已經(jīng)被加入到標準庫中去余佃,實現(xiàn)這個類型并不需要 Swift 5 的其他特性,我們使用 Swift 4 就可以自己實現(xiàn)跨算,我們一起來學習一下爆土。
1. 類型定義
public enum Result<Success, Failure: Swift.Error> {
case success(Success)
case failure(Failure)
}
以上是該類型的定義,首先它是個枚舉類型诸蚕,有兩種值分別代表成功和失敳绞啤氧猬;其次它有兩個泛型類型參數(shù),分別代表成功的值的類型以及錯誤類型坏瘩;錯誤類型有一個類型約束盅抚,它必須實現(xiàn) Swift.Error
協(xié)議。
盡管這個類型設計看起來很簡單倔矾,但它也是經(jīng)過慎重考慮的妄均,簡單討論一下其他兩種類似的設計。
public enum Result<Success, Failure> {
case success(Success)
case failure(Failure)
}
上面這個設計取消了錯誤類型的約束哪自,它有可能變相鼓勵用一個非 Swift.Error
的類型代表錯誤丰包,比如 String
類型,這與 Swift 的現(xiàn)有設計背道而馳壤巷。
public enum Result<Success> {
case success(Success)
case failure(Swift.Error)
}
第三種設計其實在很多第三方庫中出現(xiàn)邑彪,對于failure 的情況僅用了 Swift.Error
類型進行約束。它的缺點是在實例化 Result
類型時候若用的是強類型的類型胧华,會丟掉那個具體的強類型信息寄症。
2. 異步回調(diào)的應用
比如以下這個URLSession的 dataTask
方法
func dataTask(with url: URL, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask
在 Swift 5 中可以考慮被設計成:
func dataTask(with url: URL, completionHandler: @escaping (Result<(URLResponse, Data), Error>) -> Void) -> URLSessionDataTask
可以如下應用:獲取到結果后,解包撑柔,根據(jù)成功或失敗走不同路徑瘸爽。
URLSession.shared.dataTask(with: url) { (result: Result<(URLResponse, Data), Error>) in
case .success(let response):
handleResponse(response.0, data: response.1)
case .failure(let error):
handleError(error)
}
}
3. 同步 throws 函數(shù)的應用
在很多時候您访,我們并不喜歡在調(diào)用 throws
函數(shù)的時候直接處理 try
catch
铅忿,而是不打斷控制流地將結果默默記錄下來,因此這里包裝類型 Result
也能派上用處灵汪。它提供了如下這個初始化函數(shù)檀训。
extension Result where Failure == Swift.Error {
public init(catching body: () throws -> Success) {
do {
self = .success(try body())
} catch {
self = .failure(error)
}
}
}
我們可以這樣使用:
let config = Result {try String(contentsOfFile: configuration) }
// do something with config later
說到這里,大家可能會有個疑問享言,Result
類型那么方便峻凫,在設計方法的時候直接返回 Result
,而不使用 throws
可不可以览露?
簡單來說荧琼,不推薦。這是個設計問題差牛,用Result
的形式也會有不方便的情況命锄。
第一個代價是:try
catch
控制流不能直接使用了
第二個代價是:這跟 rethrows
函數(shù)設計也不默認匹配
throws
代表的是控制流語法糖,而 Result
代表的是狀態(tài)偏化。這兩者很多情況下是可以轉換的脐恩,上面說了 throws
轉成 Result
,下面看一下 Result
如何轉成 throws
侦讨,Result
的 get
方法:
public func get() throws -> Success {
switch self {
case let .success(success):
return success
case let .failure(failure):
throw failure
}
}
throws
或者是 返回Result
這兩種方式都是可行的驶冒,所以標準庫可能才猶猶豫豫那么久才決定加進去苟翻,因為帶來的可能是設計風格的不一致的問題。
一般情況下:推薦設計的時候使用 throws
骗污,在使用需要的時候轉成狀態(tài) Result
崇猫。
4. Functor 和 Monad
Functor 和 Monad 都是函數(shù)式編程的概念。簡單來說身堡,Functor
意味著實現(xiàn)了 map
方法邓尤,而Monad
意味著實現(xiàn)了flatMap
。因此 Optional
類型和 Array
類型都既是 Functor 又是 Monad贴谎,與Result
一樣汞扎,它們都是一種復合類型,或者叫 Wrapper 類型擅这。
map
方法:傳入的 transform 函數(shù)的 入?yún)⑹?Wrapped 類型澈魄,返回的是 Wrapped 類型
flatMap
方法:傳入的 transform 函數(shù)的 入?yún)⑹?Wrapped 類型,返回的是 Wrapper 類型
我們可以在這篇文章中 Swift 4.1 新特性 (2) Sequence.compactMap 可以找到關于 Optional
和 Array
的 map
仲翎、flatMap
函數(shù)的討論痹扇。
Result
作為 Functor 和 Monad 類型有 map
, mapError
, flatMap
, flatMapError
四個方法,實現(xiàn)如下:
public func map<NewSuccess>(
_ transform: (Success) -> NewSuccess
) -> Result<NewSuccess, Failure> {
switch self {
case let .success(success):
return .success(transform(success))
case let .failure(failure):
return .failure(failure)
}
}
public func mapError<NewFailure>(
_ transform: (Failure) -> NewFailure
) -> Result<Success, NewFailure> {
switch self {
case let .success(success):
return .success(success)
case let .failure(failure):
return .failure(transform(failure))
}
}
public func flatMap<NewSuccess>(
_ transform: (Success) -> Result<NewSuccess, Failure>
) -> Result<NewSuccess, Failure> {
switch self {
case let .success(success):
return transform(success)
case let .failure(failure):
return .failure(failure)
}
}
public func flatMapError<NewFailure>(
_ transform: (Failure) -> Result<Success, NewFailure>
) -> Result<Success, NewFailure> {
switch self {
case let .success(success):
return .success(success)
case let .failure(failure):
return transform(failure)
}
}