Swift 小技巧(三):如何用 Swift 思维设计网络请求

近来在用 Swift 开发 App 的过程中,最大的心得就是:我开始渐渐用「Swift 思维」来思考了。回顾刚开始我用 Swift 时,只是套用它的语法而已,脑子里依然是 Objective-C 思维。

这段时间,随着对 Swift 基本特性的掌握,我开始有意识地学习并尝试一些 Swift 才有的特性,此谓「Swift 思维」。Swift 有很多专有(Objective-C 没有的)的模式,今天我就从一个很简单的例子讲起,那就是:

如何用 Swift 思维设计网络请求。

做过网络类应用的同学应该都知道,我们做一个网络请求时,通常会有两个结果:一个是失败,返回错误,一个是成功,返回结果。当然途中还会有更复杂的情况,比如:1、网络请求本身的失败(比如网络超时);2、API 端返回的内部结果型失败(比如密码不正确)。这里我们就不细分了。

在传统的 Objective-C 的项目里,有几种处理这种异常的情况,主要是:

1、直接判断 NSError

NSError *error = nil;

id result = [API doSomething:&error];

if (error != nil) {
   NSLog("Oh Error!")
}

这种处理太直白,一般会阻塞当前的线程,因而不推荐。一般用的比较多的是下面两种:

2、通过 success,failture 的 block 来处理

[API doSomethingWithSuccess:^(id result) {

    NSLog(@"Seems good")

} failure:^(NSError *error) {

    NSLog(@"Oh Error!")

}];

这种就相对好点了,通过 Block 及内部实现,可以做到不阻塞当前线程,在有结果的时候再进行处理。在对应的 Block 处理对应的情况:成功 or 失败。 不过这个设计依然还有一点缺陷,因为它对结果的处理分散在不同的 Block,如果我需要统一处理无论成功或失败的情况,那么需要分别调用,不是太直观了。所以,我们还有第三种模式。

3、通过统一的 completionHander 的 block 来处理

[API doSomethingWithCompletionHandler:^(id result, NSError *error) {
    if result != nil {
        NSLog(@"Seems good")
    }

    if error != nil {
        NSLog(@"Oh Error!")
    }
}];

这种通过 CompletionHandler 来统一作成功结果和错误失败的处理应该是现在设计的首先,包括系统自己的 API 也是这样设计的。特别适合在一个 Block 里就把所有情况处理掉的需求。

Swift 式网络请求处理

简单列举了三种 Objective-C 下常见的网络请求类处理方式,看起来还不错,那么 Swift 模式是什么样的,能做好更好吗?我觉得是的。

Swift 里有着非常棒的 enum 机制,所有的枚举情况不但可以是任何类型,而且可以是不一样的类型。这意味着,我们在 Swift 里可以包装一种结果型 enum,比如:

enum Result {
    case Error(NSError)
    case Value(JSON)

    init(_ e: NSError?, _ v: JSON) {
        if let ex = e {
            self = Result.Error(ex)
        } else {
            self = Result.Value(v)
        }
    }
}

这段是我真实世界的代码,用在了我的微博客户端里。

代码很简单,我定义了一个名为「Result」的 enum 对象,它会包装两种情况,一种是 Value,在网络请求成功时,它就是一个 JSON 值;第二种时 Error,是一个 NSError 值,在网络请求失败时,包含着具体的错误信息。

这样,就成功地把一个网络请求下的可能的两种情况包装在了一个 Result 对象里,这个对象,要么是成功的结果,要么就是失败的错误,永远不会有同时有结果和错误。于是,我们的网络请求处理代码可以更为简单的设计成这样:

API.doSomethingWithCompletionHandler({ (result) -> Void in
    switch (result) {
    case let .Error(e):
        NSLog("Oh Error!")
    case let .Value(json):
        NSLog("Seems good")
    }
})

看起来似乎和前面 Objective-C 的第二种模式一样?似乎又像第三种?估且称之为混合模式吧。让我来简单说说这种模式有什么好处:

首先,我们通过 Switch 条件判断这种非此即彼的模式,我们可以减少很多错误的发生,保证条件分支判断不会出问题;其次,我们依然只是在一个 Closure (这里换成 Swift 术语,而不是 Objective-C 的 Block)处理我们的一个请求结果 result,因而可以在前前后后做些其他对结果的统一处理,保证我们逻辑的统一性。

这就是我所认为的 Swift 这种模式的好处了。

通过这种模式改造我的项目后,我觉得代码变得更整洁、逻辑清晰,也不会有遗失的错误处理情况。当然我做采用的还只是简化版的 Swift 模式网络处理,更为强大的例子大家可以参考 swiftz 项目的 Result 对象,它使用了 Swift 的 Generic 特性,从而使其可以包装任意值(不仅仅是 JSON),从而大大增强了扩展性:https://github.com/typelift/swiftz/blob/master/swiftz_core/swiftz_core/Result.swift#L12

我依然还在用 Swift 思维改造项目的过程中,目前这种模式应该还是有改进余地的,希望能和大家做更多有关这个的讨论与交流。

<推广> Manico 是一个专门为 OS X 高效率人士设计的 App 启动与切换工具,使用它将加倍电脑日常使用的效率。

No Comment

Leave a Comment