如何在Swift中使用Result
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 inif 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 inif let error = error {handler(.failure(error))} else {handler(.success(data ?? Data()))}}}
}
请注意,通过忽略默认API的URLResponse
值(在闭包中使用下划线而不是其参数名称),我们在上面做了一些简化。尽管对于更简单的网络任务,可能不需要检查该响应值,但这并不是我们始终想要做的事情。
如果现在返回到先前的调用站点并对其进行更新以使用新的API,我们可以看到我们的代码变得更加清晰了-因为我们现在可以为success
和failure
案例编写完全独立的代码路径,如下所示:
let task = URLSession.shared.dataTask(with: url) { result inswitch 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>) -> Voidvar session = URLSession.sharedfunc loadImage(at url: URL,then handler: @escaping Handler) {let task = session.dataTask(with: url) { result inswitch result {case .success(let data):if let image = UIImage(data: data) {handler(.success(image))} else {handler(.failure(.invalidData))}case .failure(let error):handler(.failure(.networkFailure(error)))}}task.resume()}
}
然后,上述设计使我们能够在使用图像加载器时以更精细的方式处理每个潜在的错误,例如:
let imageURL = URL(string: "https://www.swiftbysundell.com/images/logo.png")!
let imageLoader = ImageLoader()imageLoader.loadImage(at: imageURL) { result inswitch result {case .success(let image):// Handle image...case .failure(.invalidData):// Handle an invalid data failure...case .failure(.networkFailure(let error)):// Handle any network error...}
}
Swift的内置Result
类型可能只需要声明几行代码,但是它使我们能够采用的模式非常强大,并且可以导致更简单的代码——尤其是在执行异步操作(例如网络调用)时。
Result优点
Swift的类型系统的一大好处是,它使我们在处理各种操作的值和结果时消除了很多歧义。借助泛型和关联的枚举值之类的功能,我们可以轻松创建类型,以使我们能够利用编译器来确保以正确的方式处理值和结果。
SE-0235在标准库中引入了一种Result
类型,为我们提供了一种更简单,更清晰的方式来处理诸如异步API之类的复杂代码中的错误。自Swift诞生以来,人们一直在要求这些东西,因此很高兴看到它最终在Swift 5中出现!
Swift的Result
类型实现为具有两种案例的枚举:success
和failure
。两者都是使用泛型实现的,因此它们可以有一个您选择的关联的值,但failure
必须遵循Swift的Error
类型。如果需要,您可以使用特定的错误类型,例如NetworkError
或AuthenticationError
,允许我们在Swift中首次类型化throws,但这不是必需的。
演示说明
为了演示Result
,我们可以编写一个连接到远程服务器的函数,以计算有多少未读消息正在等待用户。在此示例代码中,我们将仅有一个可能的错误,即所请求的URL字符串无效:
enum NetworkError: Error {case badURL
}
获取数据的函数将接受URL字符串作为其第一个参数,并将完成处理作为其第二个参数。该完成处理本身将接受一个 Result
,其中成功案例将存储一个整数,该整数表示存在多少未读消息,而失败案例将为 NetworkError
。我们实际上并不打算在这里连接到服务器,但是使用完成处理至少可以让我们模拟异步代码——如果我们进行真正的联网,尝试直接返回一个值将会导致UI冻结。
代码:
func fetchUnreadCount1(from urlString: String, completionHandler: @escaping (Result<Int, NetworkError>) -> Void) {guard let url = URL(string: urlString) else {completionHandler(.failure(.badURL))return}// complicated networking code here print("Fetching \(url.absoluteString)...")completionHandler(.success(5))
}
要使用该代码,我们需要检查Result
内部的值,以查看调用成功还是失败,如下所示:
fetchUnreadCount1(from: "https://www.hackingwithswift.com") { result inswitch result {case .success(let count):print("\(count) unread messages.")case .failure(let error):print(error.localizedDescription)}
}
即使在这种简单的情况下, Result
也提供了两个好处。首先,我们获得的错误现在是强类型:必须为 NetworkError
。Swift的常规抛出函数未经检查,因此可以抛出任何类型的错误。因此,如果添加一个 switch
块来检查其案例,那么即使不可能也需要添加default
案例。借助Result
的强类型错误,我们可以通过列出错误枚举的所有案例来创建穷举switch
块。
其次,这是现在很清楚,我们将取回要么是成功的数据要么是错误——这是不可能得到两个或者两者都不是的。如果我们使用传统的Objective-C方法对完成处理进行重写 fetchUnreadCount1()
,则可以看到这第二个好处的重要性:
func fetchUnreadCount2(from urlString: String, completionHandler: @escaping (Int?, NetworkError?) -> Void) {guard let url = URL(string: urlString) else {completionHandler(nil, .badURL)return}print("Fetching \(url.absoluteString)...")completionHandler(5, nil)
}
在这里,完成处理期望同时接收到整数和错误,尽管其中有可能为nil。Objective-C之所以使用这种方法,是因为它没有能力表达具有关联的枚举值,因此别无选择,只能将两者都发送回去,并让用户在调用站点进行查找。
但是,这种旧方法意味着我们已经从两种可能的状态变成了四种:非错误整数,非整数错误,错误和 整数以及非整数非错误。最后两个应该是不可能的状态,但是在Swift引入Result
之前没有简单的方法来表达这一点 。
这种情况发生了很多。URLSession
的 dataTask()
方法使用相同的方式,例如:使用(Data?, URLResponse?, Error?)
调用其完成处理。这可能会给我们一些数据,一个响应和一个错误,或三者的任意组合,Swift 进化提案称这种情况“尴尬地不同”。
问题本身
在执行多种操作时,通常会有两个不同的结果——成功和失败。在Objective-C中,这两个结果通常是通过同时包含值和错误来建模的,例如,当操作完成时调用完成处理。但是,当转换为Swift时,这种方法的问题就很明显了——因为值和错误都必须是可选的?:
func load(then handler: @escaping (Data?, Error?) -> Void) {...
}
问题在于处理上述load
函数的结果变得非常棘手。即使error参数为 nil
,也不能保证在编译时保证我们正在寻找的数据确实存在—它也可能是nil
我们都知道,这会使我们的代码处于一种奇怪的状态。
可以认为 Result
它是超能力的 Optional
:它包装了成功的值,但也可以包装第二种表示不存在值的情况。 但是,有了Result
,这种不存在就可以传达额外数据,因为不是仅仅为nil
, 而是告诉我们出了什么问题。
状态分开
使用 Result
类型通过将每个结果转换为两个单独的状态来解决该问题,方法是使用一个包含每个状态的事例的枚举——一个用于 success
and一个用于 failure
:
enum Result<Value> {case success(Value)case failure(Error)
}
通过使我们的结果类型通用,可以轻松地在许多不同的上下文中重用它,同时仍保留完整的类型安全性。如果现在使用上面结果类型更新之前的load
函数,我们可以看到事情变得更加清晰了:
func load(then handler: @escaping (Result<Data>) -> Void) {...
}
使用Result
类型不仅可以提高代码的编译时安全性,而且还鼓励我们在调用产生结果值的API时始终添加适当的错误处理,如下所示:
load { [weak self] result inswitch result {case .success(let data):self?.render(data)case .failure(let error):self?.handle(error)}
}
现在我们都使代码更加清晰,并且消除了歧义,从而使API更加健壮和易于使用
如何在Swift中使用Result相关推荐
- 如何在Swift中串联或合并数组?
本文翻译自:How do I concatenate or merge arrays in Swift? If there are two arrays created in swift like t ...
- 如何在 Swift 中进行错误处理
作者:Olivier Halligon,原文链接,原文日期:2015-12-17 译者:JackAlan:校对:靛青K:定稿:Channe 今天的文章讲解如何在 Swift 中进行错误处理. 说实话, ...
- 如何在Swift中使用CoreData设置有用的自动完成UITextField
by Emrick Sinitambirivoutin 由Emrick Sinitambirivoutin 如何在Swift中使用CoreData设置有用的自动完成UITextField (How t ...
- 浅层学习与深层学习_深层副本与浅层副本-以及如何在Swift中使用它们
浅层学习与深层学习 by Payal Gupta 通过Payal Gupta 深层副本与浅层副本-以及如何在Swift中使用它们 (Deep copy vs. shallow copy - and h ...
- 如何在Swift中发出HTTP请求?
本文翻译自:How to make an HTTP request in Swift? I read The Programming Language Swift by Apple in iBooks ...
- 如何在Swift中创建漂亮的iOS图表
通过图形和图表呈现数据是当今移动应用程序最显着的特征之一.iOS图表使应用程序看起来更漂亮,更有吸引力. 在本教程中,我们将向您展示如何使用代码示例在Swift中实现我们的iOS图表.我们将看一下Sw ...
- swft c 语言 数组,如何在swift中实现数组的深拷贝
在Objective-C中如果想将一个数组赋值给另外一个数组,同时想让两个数组之间相互独立(即改变其中的一个数组,不影响另外的一个),有很多的办法,比如我们可以直接copy,用类方法创建新数组.这样得 ...
- swift java混合,如何在Swift中连接或合并数组?
使用Swift 3,根据您的需求和品味,您可以选择其中一个 five following ways 来连接/合并两个数组 . 1.使用Swift标准库(: :)泛型运算符将两个数组合并为一个新数组 S ...
- 如何在 Swift 中使用 CommonCrypto 类进行加密(I)
原文连接:Cryptography in Swift with CommonCrypto 原文日期:2015/08/10 译者:CMB 校对:numbbbbb 定稿:shanks 现在,许多开发者已经 ...
最新文章
- 登录linux后台工具,linux后台进程管理工具-supervisor
- 【modbus】libmodbus库的移植与使用
- 拷贝构造函数的参数类型必须是引用
- 工作流引擎Activiti使用总结
- jdk版本 linux更改was_如何在 Linux 上安装 Java
- More C++ Idioms
- SQL之用户自定义函数
- upgrade yum 指定版本_CentOS 6.9/7通过yum安装指定版本的MySQL
- oracle有没有mysql if_Oracle中没有 if exists(...)
- jquery常见操作总结
- 《Redis开发与运维》学习第五章
- dz搬家 win linux,Discuz论坛完美搬家 详细分享我的DZ搬家步骤
- 地学计算方法/地统计学(第二章地理数据及其采集与预处理)
- pig的基本语法以及高级语法
- 猫游记页游mysql_5款曾经极其火爆的页游,最后一款90后没听过80后才玩过
- 【arc093f】Dark Horse(容斥原理,动态规划,状态压缩)
- 使用vue+elementUI页面实现前端做分页
- 转型经验分享|作为传统汽车工程师,我如何转型去阿里做无人驾驶?
- caj转换word转换器怎么操作?
- 希腊罗马神话和《圣经》中的英语典故