在Swift 5 之前,抛出和处理错误的标准做法是使用 throws
try
catch
, 异步错误使用的是 completion: @escaping (ResponseType?, ErrorType?) -> Void
的形式进行回调。 然而一些第三方库已经发现了缺乏一个泛型 Result
类型的不方便,纷纷实现了自己的 Result 类型以及相关的 Monad 和Functor 特性。
Swift 5 尽管仍正在开发中,我们看到 Result
类型已经被加入到标准库中去,实现这个类型并不需要 Swift 5 的其他特性,我们使用 Swift 4 就可以自己实现,我们一起来学习一下。
1. 类型定义
public enum Result {
case success(Success)
case failure(Failure)
}
以上是该类型的定义,首先它是个枚举类型,有两种值分别代表成功和失败;其次它有两个泛型类型参数,分别代表成功的值的类型以及错误类型;错误类型有一个类型约束,它必须实现 Swift.Error
协议。
尽管这个类型设计看起来很简单,但它也是经过慎重考虑的,简单讨论一下其他两种类似的设计。
public enum Result {
case success(Success)
case failure(Failure)
}
上面这个设计取消了错误类型的约束,它有可能变相鼓励用一个非 Swift.Error
的类型代表错误,比如 String
类型,这与 Swift 的现有设计背道而驰。
public enum Result {
case success(Success)
case failure(Swift.Error)
}
第三种设计其实在很多第三方库中出现,对于failure 的情况仅用了 Swift.Error
类型进行约束。它的缺点是在实例化 Result
类型时候若用的是强类型的类型,会丢掉那个具体的强类型信息。
2. 异步回调的应用
比如以下这个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
可以如下应用:获取到结果后,解包,根据成功或失败走不同路径。
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 函数的应用
在很多时候,我们并不喜欢在调用 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
这两种方式都是可行的,所以标准库可能才犹犹豫豫那么久才决定加进去,因为带来的可能是设计风格的不一致的问题。
一般情况下:推荐设计的时候使用 throws
,在使用需要的时候转成状态 Result
。
4. Functor 和 Monad
Functor 和 Monad 都是函数式编程的概念。简单来说,Functor
意味着实现了 map
方法,而Monad
意味着实现了flatMap
。因此 Optional
类型和 Array
类型都既是 Functor 又是 Monad,与Result
一样,它们都是一种复合类型,或者叫 Wrapper 类型。
map
方法:传入的 transform 函数的 入参是 Wrapped 类型,返回的是 Wrapped 类型
flatMap
方法:传入的 transform 函数的 入参是 Wrapped 类型,返回的是 Wrapper 类型
我们可以在这篇文章中 Swift 4.1 新特性 (2) Sequence.compactMap 可以找到关于 Optional
和 Array
的 map
、flatMap
函数的讨论。
Result
作为 Functor 和 Monad 类型有 map
, mapError
, flatMap
, flatMapError
四个方法,实现如下:
public func map(
_ transform: (Success) -> NewSuccess
) -> Result {
switch self {
case let .success(success):
return .success(transform(success))
case let .failure(failure):
return .failure(failure)
}
}
public func mapError(
_ transform: (Failure) -> NewFailure
) -> Result {
switch self {
case let .success(success):
return .success(success)
case let .failure(failure):
return .failure(transform(failure))
}
}
public func flatMap(
_ transform: (Success) -> Result
) -> Result {
switch self {
case let .success(success):
return transform(success)
case let .failure(failure):
return .failure(failure)
}
}
public func flatMapError(
_ transform: (Failure) -> Result
) -> Result {
switch self {
case let .success(success):
return .success(success)
case let .failure(failure):
return transform(failure)
}
}