Swift 5.0 值得關注的特性:增加 Result 列舉型別
在非同步獲取資料的場景中,常見的回撥的資料結構是這樣的:表示獲取成功的資料,表示獲取失敗的 error。因為資料可能獲取成功,也可能失敗。因此回撥中的資料和錯誤都是 optional 型別。 比如 CloudKit 中儲存資料的一個函式就是這樣:
func save(_ record: CKRecord, completionHandler: @escaping (CKRecord?, Error?) -> Void) 複製程式碼
這種形式的缺點是沒有體現出兩種結果的互斥關係:如果資料成功獲取到了,那麼 error 一定為空。如果 error 有值,資料一定是獲取失敗了。
Swift 中列舉的能力相比 OC 有著很大的進步,每個列舉值除了可以是常規的基礎型別,還可以是一個關聯的型別。有了這樣的特性後用列舉來優化返回結果的資料結構顯得水到渠成:
enum Result<Success, Failure> where Failure : Error { /// A success, storing a `Success` value. case success(Success) /// A failure, storing a `Failure` value. case failure(Failure) } 複製程式碼
基本用法
定義非同步返回結果是 Int 型別的函式:
func fetchData(_ completionHandler: @escaping (Result<Int, Error>) -> Void) { DispatchQueue.global().async { let isSuccess = true if isSuccess { let resultValue = 6 return completionHandler(.success(resultValue)) } else { let error = NSError(domain: "custom error", code: -1, userInfo: nil) return completionHandler(.failure(error)) } } } 複製程式碼
返回值的型別通過泛型進行約束,Result
第一個泛型型別表示返回值的型別,第二個型別表示錯誤的型別。對Result
賦值和常規的列舉一樣:
let valueResult: Result<Int, CustomError> = Result.success(4) // 因為 swift 中會進行型別推斷,編譯器在確認返回的是 `Result` 型別後,可以省略列舉型別的宣告 let errorResult = .failure(CustomError.inputNotValid) 複製程式碼
取出Result
值和獲取普通的關聯型別列舉是一樣的:
fetchData { (result) in switch result { case .success(let value): print(value) case .failure(let error) print(error.localizedDescription) } } 複製程式碼
如果你只想要獲取其中一項的值,也可以直接用 if case 拆包:
fetchDate { (result) in if case .success(let value) = result { print(value) } } 複製程式碼
可以判等
Enum 是一個值型別,是一個值就應該可以判斷是否相等。如果Result
的成功和失敗的型別都是Equatable
,那麼Result
就可以判等,原始碼如下:
extension Result : Equatable where Success : Equatable, Failure : Equatable { } 複製程式碼
類似的,如果是成功和失敗的型別都是Hashable
,那麼Result
也是Hashable
:
extension Result : Hashable where Success : Hashable, Failure : Hashable { } 複製程式碼
如果實現了Hashable
,可以用來當做字典的 key。
輔助的 API
map、mapError
與 Dictionary 類似,Swift 為Result
提供了幾個 map value 和 error 的方法。
let intResult: Result<Int, Error> = Result.success(4) let stringResult = x.map { (value) -> Result<String, Error> in return.success("map") } let originError = NSError(domain: "origin error", code: -1, userInfo: nil) let errorResult: Result<Int, Error> = .failure(originError) let newErrorResult = errorResult.mapError { (error) -> Error in let newError = NSError(domain: "new error", code: -2, userInfo: nil) return newError } 複製程式碼
flatMap、flatMapError
map 返回的是具體的結果和錯誤, flatMap 閉包中返回的是Result
型別。如果Result
中包含的是資料,效果和 map 一致,替換資料;如果Result
中包含的是錯誤,那麼不替換結果。
let intResult: Result<Int, Error> = Result.success(4) // 替換成功 let flatMapResult = intResult.flatMap { (value) -> Result<String, Error> in return.success("flatMap") } // 沒有執行替換操作,flatMapIntResult 值還是 intResult let flatMapIntResult = intResult.flatMap { (value) -> Result<String, Error> in return.failure(NSError(domain: "origin error", code: -1, userInfo: nil)) } 複製程式碼
get
很多時候只關心Result
的值,Swift 提供了get()
函式來便捷的直接獲取值,需要注意的是這個函式被標記為throws
,使用時語句前需要加上try
:
let intResult: Result<Int, Error> = Result.success(4) let value = try? intResult.get() 複製程式碼
可丟擲異常的閉包初始化器
很多時候獲取返回值的閉包中可能會發生異常代表獲取失敗的錯誤,基於這個場景 Swift 提供了一個可丟擲異常的閉包初始化器:
enum CustomError: Error, Equatable { case inputNotValid } let fetchInt = { () -> Int in if true { return 4 } else { throw CustomError.inputNotValid } } let result: Result<Int, Error> = Result { try fetchInt() } 複製程式碼
需要提醒是通過這種方式宣告的Result
的 error 型別只能是Error
,不能指定特定的Error
。
-
微博:(weibo.com/1926303682)
-
如果想與我有更密切的交流也可以加入我的知識星球:(t.zsxq.com/JEiqj27 )