内容简介:从 Swift 2 开始,同步抛出错误的标准做法是使用Swift 5 已经伴随 Xcode 10.2 正式发布,我们看到以上是该类型的定义,首先它是个枚举类型,有两种值分别代表成功和失败;其次它有两个泛型类型参数,分别代表成功的值的类型以及错误类型;错误类型有一个类型约束,它必须实现
从 Swift 2 开始,同步抛出错误的标准做法是使用 throws/throw
,处理是用 do/try/catch
;异步错误使用的是 completion: @escaping (ResultType?, ErrorType?) -> Void
的形式进行回调。 然而一些第三方库已经发现了缺乏一个泛型 Result<Success,Failure>
类型的不方便,纷纷实现了自己的 Result 类型以及相关的 Monad 和 Functor 特性。
Swift 5 已经伴随 Xcode 10.2 正式发布,我们看到 Result<Success, Failure: Error>
类型已经被加入到标准库中去,它有哪些设计考虑,如何使用,由浅入深地一起来了解一下吧。
1. Result 类型定义和设计
public enum Result<Success, Failure: Swift.Error> { case success(Success) case failure(Failure) } 复制代码
以上是该类型的定义,首先它是个枚举类型,有两种值分别代表成功和失败;其次它有两个泛型类型参数,分别代表成功的值的类型以及错误类型;错误类型有一个类型约束,它必须实现 Swift.Error
协议。
尽管这个类型设计看起来很简单,但它也是经过慎重考虑的,简单讨论一下其他两种类似的设计。
public enum Result<Success, Failure> { case success(Success) case failure(Failure) } 复制代码
上面这个设计取消了错误类型的约束,它有可能变相鼓励用一个非 Swift.Error
的类型代表错误,比如 String
类型,这与 Swift 的现有设计背道而驰。
public enum Result<Success> { case success(Success) case failure(Swift.Error) } 复制代码
第三种设计其实在很多第三方库中出现,对于 failure 的情况仅用了 Swift.Error
类型进行约束。它的缺点是在实例化 Result
类型时候若用的是强类型的类型,会丢掉那个具体的强类型信息。
2. Result 类型在异步回调函数中的应用
比如以下这个URLSession的 dataTask
方法
func dataTask(with url: URL, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask 复制代码
在 Swift 5 中可以考虑被设计成:
func dataTask(with url: URL, completionHandler: @escaping (Result<Data, Error>, URLResponse?) -> Void) -> URLSessionDataTask 复制代码
可以如下应用:获取到结果后,解包,根据成功或失败走不同路径。
URLSession.shared.dataTask(with: url) { (result, _ in switch(result) { case .success(let data): handleResponse(data) case .failure(let error): handleError(error) } } } 复制代码
这样的 API 设计更清楚地传递了 API 上的约束,相比较原来的设计:
-
Data
和Error
有且仅有一个为空,另一个有值 -
任何情况下
URLResponse
都可能存在或为空
3. Result 类型与同步 throws 函数
在很多时候,我们并不喜欢在调用 throws
函数的时候直接处理 try
catch
,而是不打断控制流地将结果默默记录下来,因此这里包装类型 Result
也能派上用处。它提供了如下这个初始化函数。
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
函数设计也不默认匹配
throws
代表的是控制流语法糖,而 Result
代表的是结果。这两者是可以转换的,上面介绍了 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
这两种方式都是可行的,所以标准库可能才犹犹豫豫那么久才决定加进去,因为带来的可能是设计风格的不一致的问题。
一般情况下:推荐设计同步 API 的时候仍旧使用 throws
,在使用需要的时候转成状态 Result
。
4. Functor (map) 和 Monad (flatMap)
Functor 和 Monad 都是函数式编程的概念。简单来说,Functor 意味着实现了 map
方法,而 Monad 意味着实现了 flatMap
。
因此, Result
与 Optional
类型和 Array
类型一样,都既是 Functor 又是 Monad,它们都是一种复合类型,或者叫 Wrapper 类型。
map
方法:传入的 transform 函数的 入参是 Wrapped 类型,返回的是 Wrapped 类型
flatMap
方法:传入的 transform 函数的 入参是 Wrapped 类型,返回的是 Wrapper 类型
Result
作为 Functor 和 Monad 类型有 map
, mapError
, flatMap
, flatMapError
四个方法,实现如下:
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) } } 复制代码
5. do/try/catch 是个语法糖
我们有多个同步返回的 Result
的函数进行连续调用,如果每个结果都直接用 pattern matching 来解,那么很容易形成 pattern matching 的多层嵌套。 我们来看一下 Result.flatMap
是如何帮助解决这个问题的:
func fetchImageData(from url: URL) -> Result<Data, Error> { return Result(catching: {try Data(contentsOf: url)}) } func process(image: Data) -> Result<UIImage, Error> { if let image = UIImage(data: image) { return .success(image) } else { return .failure(ImageProcessingError.corruptedData) } } func persist(image: UIImage) -> Result<Void, Error> { return .success(()) } let result = fetchImageData(from: url) .flatMap(process) .flatMap(persist) switch result { case .success: // do something break case .failure(ImageProcessingError.corruptedData): // do something break case .failure(CocoaError.fileNoSuchFile): // do something break default: // do something break } 复制代码
在这个例子中,我们看到了 flatMap
帮助串起了流程,将一种 Success,通过执行函数转换成 NewSuccess,而 Error 是按原样进行传递。如果发生了 Error,那么最终得到的 Error 就是第一个 Error,整个流程终止。
上述代码从功能上,是否跟 do/try/catch
所能做到的很像,几乎一模一样?形式上是否也跟 do/try/catch
十分相似呢? 我们来比照一下:
func fetchImageData(from url: URL) throws -> Data { return try Data(contentsOf: url) } func process(image: Data) throws -> UIImage { if let image = UIImage(data: image) { return image } else { throw ImageProcessingError.corruptedData } } func persist(image: UIImage) throws{ } do { let data = try fetchImageData(from: url) let image = try process(image: data) try persist(image: image) } catch ImageProcessingError.corruptedData{ } catch CocoaError.fileNoSuchFile { } catch { } 复制代码
这样的相似性证实了两点:
-
do/try/catch
的实质是类似于Result.flatMap
的语法糖 -
使用
do/try/catch
处理起来更简练和灵活,因此一般情况下的同步函数错误抛出 API 仍旧推荐使用throw/throws
的形式
6. 搞特殊化:Error 实现了 Error?
我们在上面的代码中看到了返回类型 Result<Data, Error>
,但是如果按照 Result
的定义 Result<Success, Failure: Swift.Error>
来看,这不能是个合法的类型,因为 Swift 规定协议本身并没有实现协议。我们可以通过下面的代码来证明:
struct A<T: K> {} protocol K { func doIt() } // 编译错误 Protocol type 'K' cannot conform to 'K' because only concrete types can conform to protocols let a = A<K>() struct B<T: Error> {} // 编译通过 let b = B<Error>() 复制代码
这里的编译错误是:K 协议本身没有实现 K 协议,仅有实际类型能实现接口。但 K 如果改成 Error 的话,则可以编译过。这证明了 Error
的特殊性,它被认为实现了协议本身。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 『互联网架构』软件架构-redis特性和集群特性(中)(49)
- 『互联网架构』软件架构-redis特性和集群特性(上)(48)
- 『互联网架构』软件架构-redis特性和集群特性(下)(50)
- JDK 14 功能特性
- C# 特性(Attribute)
- python—高级特性
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
精通Spring 4.x
陈雄华、林开雄、文建国 / 电子工业出版社 / 2017-1-1 / CNY 128.00
Spring 4.0是Spring在积蓄4年后,隆重推出的一个重大升级版本,进一步加强了Spring作为Java领域第一开源平台的翘楚地位。Spring 4.0引入了众多Java开发者翘首以盼的基于Groovy Bean的配置、HTML 5/WebSocket支持等新功能,全面支持Java 8.0,最低要求是Java 6.0。这些新功能实用性强、易用性高,可大幅降低Java应用,特别是Java W......一起来看看 《精通Spring 4.x》 这本书的介绍吧!