Swift 5.0 值得關注的特性:增長 Result 枚舉類型

背景

在異步獲取數據的場景中,常見的回調的數據結構是這樣的:表示獲取成功的數據,表示獲取失敗的 error。由於數據可能獲取成功,也可能失敗。所以回調中的數據和錯誤都是 optional 類型。 好比 CloudKit 中保存數據的一個函數就是這樣:程序員

func save(_ record: CKRecord, completionHandler: @escaping (CKRecord?, Error?) -> Void)
複製代碼

這種形式的缺點是沒有體現出兩種結果的互斥關係:若是數據成功獲取到了,那麼 error 必定爲空。若是 error 有值,數據必定是獲取失敗了。swift

Swift 中枚舉的能力相比 OC 有着很大的進步,每一個枚舉值除了能夠是常規的基礎類型,還能夠是一個關聯的類型。有了這樣的特性後用枚舉來優化返回結果的數據結構顯得水到渠成:api

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 值和獲取普通的關聯類型枚舉是同樣的:dom

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就能夠判等,源碼以下:async

extension Result : Equatable where Success : Equatable, Failure : Equatable { }
複製代碼

相似的,若是是成功和失敗的類型都是 Hashable,那麼 Result 也是 Hashable函數

extension Result : Hashable where Success : Hashable, Failure : Hashable { }
複製代碼

若是實現了 Hashable ,能夠用來當作字典的 key。fetch

輔助的 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


延伸閱讀:


相關文章
相關標籤/搜索