在异步获取数据的场景中,常见的回调的数据结构是这样的:表示获取成功的数据,表示获取失败的 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
与 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
}
复制代码
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))
}
复制代码
不少时候只关心 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
。
延伸阅读: