Result介绍
Swift标准库的Result
类型使我们能够使用单个统一类型来表达给定操作的结果(无论成功还是失败)。让我们看一下在哪种情况下Result
可能有用的方法,以及一些在开始使用该类型时要牢记的技巧和窍门。
尽管有很多不同的方法可以对Result
类型进行建模,但是Swift标准库中内置的方法被声明为通用枚举,它针对结果可能包含的成功值以及遇到的任何错误进行了强类型化。看起来像这样:
enum Result<Success, Failure> where Failure: Error {
case success(Success)
case failure(Failure)
}
就像上面的声明所示,Result
只要Failure
类型符合Swift的Error协议,我们就可以用来表示任何成功/失败组合。那么我们如何在实践中使用上述类型,这样做的好处是什么?
例如,让我们看一下URLSession
,它是最常用的API之一——使用基于闭包的设计以异步方式返回网络请求的各种结果:
let url = URL(string: "https://www.swiftbysundell.com")!
let task = URLSession.shared.dataTask(with: url) {
data, response, error in
if let error = error {
// Handle error
...
} else if let data = data {
// Handle successful response data
...
}
}
task.resume()
尽管URLSession
这些年来已经发展了很多,并且拥有一套功能强大的API,但确切地决定如何处理网络调用的结果有时会有些棘手。因为,如上面的示例所示,data
和error
结果一样作为可选参数传递到我们的闭包中-这反过来要求我们在每次进行网络调用时都要解开每个值。
让我们看看使用Result
可以帮助我们如何解决该问题。我们将从扩展URLSession
新API 入手,该API将一个Result<Data, Error>
值传递到其完成处理程序中,而不是一组可选参数。为了实现这一点,我们将对标准API提供给我们的可选选项进行解包(类似于上面的操作),以构造我们的代码Result
,如下所示:
extension URLSession {
func dataTask(
with url: URL,
handler: @escaping (Result<Data, Error>) -> Void
) -> URLSessionDataTask {
dataTask(with: url) {
data, _, error in
if let error = error {
handler(.failure(error))
} else {
handler(.success(data ?? Data()))
}
}
}
}
请注意,通过忽略默认API的URLResponse
值(在闭包中使用下划线而不是其参数名称),我们在上面做了一些简化。尽管对于更简单的网络任务,可能不需要检查该响应值,但这并不是我们始终想要做的事情。
如果现在返回到先前的调用站点并对其进行更新以使用新的API,我们可以看到我们的代码变得更加清晰了-因为我们现在可以为success
和failure
案例编写完全独立的代码路径,如下所示:
let task = URLSession.shared.dataTask(with: url) {
result in
switch result {
case .success(let data):
// Handle successful response data
...
case .failure(let error):
// Handle error
...
}
}
关于Result
上面使用方式的一个有趣的细节是,我们Failure
简单地将其类型指定为Error
。这意味着任何错误都可以传递到我们的结果中,这反过来又限制了我们在呼叫站点进行更具体的错误处理的选项(因为我们没有任何要处理的潜在错误的详尽列表)。直接与系统API结合使用时,要改变这一点比较棘手,而这又会引发任何错误-当我们构建更具体的抽象形式时,我们通常可以为其设计一个更统一的错误API 。
例如,假设我们正在构建一个非常简单的图像加载器,可以再次使用来通过网络加载图像URLSession
。但是在开始实际实现加载程序本身之前,让我们首先定义一个枚举,该枚举列出它可能遇到的所有潜在错误。目前,我们只有两种情况——发生了网络错误,或者我们下载的数据被证明是无效的:
enum ImageLoadingError: Error {
case networkFailure(Error)
case invalidData
}
然后,在构建图像加载器时,我们现在可以专门Result
处理上述错误类型-这又使我们能够向呼叫站点发送更多的错误信息:
struct ImageLoader {
typealias Handler = (Result<UIImage, ImageLoadingError>) -> Void
var session = URLSession.shared
func loadImage(at url: URL,
then handler: @escaping Handler) {
let task = session.dataTask(with: url) {
result in
switch result {
case .success(let data):
if let image = UIImage(data: data) {
handler(.success(image))
} else {
handler(.failure(.invalidData))
}
case .