在我们设计网络层响应结果的时候,Result是大家用的比较多的。和Optional有点类似,它是用来表述返回类型是成功或者失败。在Swift5.0中,Swift团队已经把Result加入到了标准库。这篇文章将讲解到何时去使用Result,以及怎样使用Result。
上篇文章,我们讲到Swift异常处理,在处理时候用do{}catch{}捕捉异常,以及在方法名后通过throws关键字将异常往上层抛。和抛错误不同的是,在代码中用Result作为返回类型可以达到我们需要的效果。更大的不同是,Result可以用于在异步处理时候,如鱼得水般地优雅处理成功与失败的回调返回。在讲解之前,我们先看一下以下片段1代码:

1
2
3
4
5
6
7
8
9
10
func loadData(from url: URL, completion:@escaping (Data?) -> Void) throws {
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
throw error
}
if let data = data {
completion(data)
}
}.resume()
}

按照以往的方式,当error有异常时候,我们进行抛出异常,然后方法上也加上throws。假设以上代码编译通过,那么在调用的时候代码片段如下:
1
2
3
4
5
6
7
8
9
do {
try loadData(from: aURL) { data in
print("fetched data")
}

print("This will be executed before any data is fetched from the network.") //这行代码会被先执行。由于loadData方法是异步处理请求数据,就算发生错误,error抛出存在一定的执行时长。这就违背了异常捕捉而中止的性质了,也谈不上异常捕捉。
} catch {
print(error)
}


以上片段1代码编译显然报错,很不幸,dataTask处理方法API并没有throws关键字。那要怎么处理异常呢?这个时候Result派上用场了。在讲解Result之前,我们首先来看一下Result的核心源码。
1
2
3
4
5
6
7
8
9
10
/// A value that represents either a success or a failure, including an
/// associated value in each case.
@frozen public 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)
}

然后用Result进行改造,这样就很友好地处理了成功和异常的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func loadData(from url: URL, completion:@escaping (Result<Data, URLError>) -> Void) {
URLSession.shared.dataTask(with: url) { data, response, error in
if let urlError = error as? URLError {
completion(.failure(urlError))
}

if let data = data {
completion(.success(data))
}
}.resume()
}

loadData(from: URL(string: "http://api.littleWhale/myAvatar")!) { result in
guard let data = try? result.get() else {
return
}
print(data)
}

继续再看一例子:拿到data,需要将它转成图片。
1
2
3
4
5
6
7
8
9
10
11
12
13
enum ConversionFailure: Error {
case invalidData
}

func convertToImage(_ data: Data, completionHandler: @escaping (Result<UIImage, ConversionFailure>) -> Void) {
DispatchQueue.global(qos: .userInitiated).async {
if let image = UIImage(data: data) {
completionHandler(.success(image))
} else {
completionHandler(.failure(ConversionFailure.invalidData))
}
}
}

这里,我们自定义了一个错误类型。然后通过GCD异步方式将data转化成image。这里我们使用了Result 作为返回类型。当流转成了图片则返回图片,否则返回ConversionFailure错误。
然后我们再看调用:
1
2
3
4
5
6
7
8
convertToImage(data) { result in
switch result {
case .success(let image):
print("we get an image!")
case .failure(let error):
print("invalid data error! \(error)")
}
}

这里我们用switch语句对result枚举类型判断.success 和 .failure两种类型。
你也可以用result.get()方法获得返回的结果要么是成功,要么是失败。失败error可以被do{}catch{}捕捉到:
1
2
3
4
5
6
7
8
convertToImage(data) { result in
do {
let image = try result.get()
print("we get an image!")
} catch {
print("invalid data error! \(error)")
}
}

或者用try? 可能返回nil,当返回nil值时即发生了错误

1
2
3
4
5
6
convertToImage(data) { result in
guard let image = try? result.get() else {
return
}
print("we get an image!")
}

Result也提供了一些库方法,如map,mapError等,我们可以根据需要对result结果进行处理包装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
convertToImage(data) { result in
let newResult = result.map { uiImage in
return uiImage.cgImage
}
}

struct WrappedError: Error {
let cause: Error
}

convertToImage(data) { result in
let newResult = result.mapError { conversionFailure in
return WrappedError(cause: conversionFailure)
}
}

最后,我们可以对网络处理层用Result进行包装:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
protocol GYRemoteFetch {
associatedtype ResultModel:Codable
func loadData(from url: URL, completion:@escaping (Result<ResultModel, Error>) -> Void)
}
extension GYRemoteFetch {
func loadData(from url: URL, completion:@escaping (Result<ResultModel, Error>) -> Void) {
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else {
if let error = error {
completion(.failure(error))
return
}
fatalError("data can't be nil")
}

let decoder = JSONDecoder()
let result = Result(catching: {
try decoder.decode(ResultModel.self, from: data)
})
completion(result)
}.resume()
}
}

struct User:Codable{

}
//UserViewModel请求网络用户信息举例:
struct UserViewModel:GYRemoteFetch {

typealias ResultModel = User

func fetchUser(){
loadData(from: URL(string: "http://api.littleWhale/user")!) { result in
guard let user = try? result.get() else {
return
}
print(user)
}
}
}

评论