摘要

本文上半部分将为您解释为什么在实际项目中为什么不要调用 onError 以及尽量不使用 Driver 。同时给出一种合理的解决方案,让我们仍然可以愉快的传递 Error ,并对 Value 进行处理。
下半部分将介绍用函数式来精简我们的代码。

注:本文基于 Swift 3 。

忘记 onError

onError 释放资源

可能这个标题有些吼人,不是说 Rx 中的 Error 处理是很好的方案吗?可以把 Error 归到一起处理。笔者在这里也觉得这是一个很好的方案,但有一个地方非常头疼,发射 Error 后是释放对应的事件链,也就是数据流。还是用网络请求举例,比如登录。我们打算做一个登录 -》 成功 -》保存 token -》 用 token 获取用户信息等等。
在登录的部分,点击登录,进行验证,很明显,如果密码有误,
画图,

图片表达的很清晰,对应代码的代码是:

button
    .rx_tap // 点击登录
    .flatMap(provider.login) // 登录请求
    .map(savetoken) // 保存 token
    .flatMap(provider.requestInfo) // 获取用户信息
    .subscribe(handleResult) // 处理结果

代码和流程图是一个样子的,效果还不错。
运行一下,输入正确的账号密码,登录,登录成功,获取用户信息。一切正常。
但是我们来看下面这种场景,登录失败(不论是因为网络错误,还是因为密码错误之类的原因,我们都对这些错误调用了 onError 传递错误信息),直接将 error 传递到事件结尾,即显示登录错误的信息。此时再去点击登录就不会有任何提示了。
因为上面这一条点击登录事件链都被 dispose 了。

这是一个 bug 。我们不希望在第一次点击登录失败后,再次点击登录缺没什么反应。

事实上在 Try! Swift 大会上有一场 POP 的分享,Demo 地址 RxPagination 。试着把网络关了,拉取一下数据,再打开网络,再拉取一下数据看看?此时是没有什么反应的。补一句,这个项目是值得学习一下的。

用官方的方法处理 Error ?

在讨论用官方的方式处理 Error 前,我们先来确认一件事情,处理一个登录流程,如果出现了错误是否应该继续下去,答案是显然的,不继续,停止本次事件。

官方给出了一下几种操作:

  • retry

  • catchError

  • catchErrorJustReturn

  • doOnError

很可惜,前三种方法都是处理 error ,将 error 变换成正常的值继续下去,并没有停止本次事件。而 doOnError 只是在出现 error 的时候做点什么事情,并不能对事件流有什么影响。

使用 Result

enum Result<T> {
    case value(T)
    case error(ErrorProtocol)
}

Swift 中的枚举关联值是如此的强大,这可以帮我们解决 Error 的处理,如果 case 为 error ,那就不处理,将 error 传递下去即可。

相比原有的 onError 有如下优势:

  • 不因为 error 释放资源

  • 方便对 error 传递、处理

类似这样:

provider.request(GitHubAPI.Authorize)
    .map { result -> Result<String> in
        switch result {
        case .value(let value):
            return Result.value(value["token"].stringValue)
        case .error(let error):
            return Result.error(error)
        }
    }
    .flatMap { result -> Observable<Result<JSON>> in
        switch result {
        case .value(let token):
            return provider.request(GitHubAPI.Accesstoken(code: token))
        case .error(let error):
            return Observable.just(Result.error(error))
        }
    }
    .subscribeNext { json in
        // ...
    }

catch 等系列方法也可以直接在这里替代,而且更灵活了一些,可以返回任何我们想要的类型。

过多的“无用”代码

比如我们要进行多个操作,在第一个或第二个操作就可能出现 error 时,我们的代码会变得很臃肿,也就是有很多的 case .error(let error): 的代码。
这并不优雅。

摘要

在上一篇 在实践中应用 RxSwift 1 - 使用 Result 传递值中,我们解决了 error 的处理,但当我们处理一段很长的事件流时,会发现有很多不重要的代码,比如传递 Error 。本文将讨论一种优雅的方式处理该问题 - 函数式。本文结构分为两部分,第一部分讨论上一篇的 error 问题,第二部分再写一些其它的小函数,方便我们更好的写代码。

注:
本文不会为您解释过多关于函数式的内容,如果您需要了解,可以阅读 Chris 的 Functional Swift ,本书还有对应的中文版 函数式 Swift 。

enum Result<Value> {
    case value(Value)
    case error(ErrorProtocol)
}

为 Result 添加 map 和 flatMap

在上一节,我们用 Result 解决了 onError 的问题, 但缺带来了很多重复处理 Error 的代码。先来尝试下 Monad 的方案。先来写个 map

func map<T>(_ transform: (Value) throws -> T) -> Result<T> {
    switch self {
    case .value(let object):
        do {
            let nextObject = try transform(object)
            return Result<T>.value(nextObject)
        } catch {
            return Result<T>.error(error)
        }
    case .error(let error):
        return Result<T>.error(error)
    }
}

可以看到我们这个 map 的实现还是很完善的:

  • 支持对 value 的变换

  • 支持抛出 error

笔者认为这基本满足了我们的需求,传递 Error ,对 value 进行变换,抛出错误。现在我们可以把上一篇的代码改成下面这个样子:

provider
    .request(GitHubAPI.Authorize)
    .map { result in
        result.map { json in
            return json["token"].stringValue
        }
    }
    .flatMap { result -> Observable<Result<JSON>> in
        switch result {
        case .value(let token):
            return provider.request(GitHubAPI.Accesstoken(code: token))
        case .error(let error):
            return Observable.just(Result.error(error))
        }
    }
    .subscribeNext { json in
        // ...
    }

易读性仍然不够,我们继续。

在 Rx 中,mapflatMap 是最常用的,我们添加一些小工具。

mapValue

func mapValue<T,K>(_ transform: (T) throws -> K) -> (Result<T>) -> Result<K> {
    return { result in
        result.map(transform)
    }
}

于是我们对 Resultmap 操作可以变成这个样子:

.map(mapValue { json in
        return json["token"].stringValue
    }

优雅了很多,不需要再处理 error 问题了。

flatMapRequest

类似的,我们还可以对网络请求的 flatMap 下手。

func flatMapRequest<T>(_ transform: (T) -> Target) -> (Result<T>) -> Observable<Result<JSON>> {
    return { result in
        let api = result.map(transform)
        switch api {
        case .value(let value):
            return provider.request(value)
        case .error(let error):
            return Observable.just(Result.error(error))
        }
    }
}

完整的调用就变成了这个样子:

provider
    .request(GitHubAPI.Authorize)
    .map(mapValue { json in
        return json["token"].stringValue
        })
    .flatMap(flatMapRequest { token in
        return GitHubAPI.Accesstoken(code: token)
    })
    .subscribeNext { result in
        // ...
    }

注:
这里的 flatMapRequest 的 flatMap 并非真正的 flatMap ,笔者只是方便对应 Rx 中的 flatMap 操作。以此表示这个方法是用在 flatMap 上的。

其他小工具

类似上面的方式,我们还可以写一些常用的方法:

func toTrue<T>() -> T -> Bool {
    return { _ in
        return true
    }
}

再比如调用 rx_sendMessage 时,我们可能不需要参数:

func toVoid<T>() -> T -> Void {
    return { _ in }
}

只需要 Resultvalue 情况?同时获取 value

func filterValue<T>() -> Result<T> -> Observable<T> {
    return { result in
        switch result {
        case .value(let object):
            return Observable.just(object)
        case error(let error):
            return Observable.empty()
        }
    }
}

再比如只处理成功的情况:

func success<T>(_ action: (T) -> Void) -> Result<T> -> Void {
    return { result in
        result.success(action)
    }
}

甚至是带有默认错误处理方法的函数,当然这里笔者就不再赘述,有兴趣可以自行试试看~

可以看到,在实现每一个操作符(比如 map)中传入的闭包,尝试这样函数式的代码,会减少写很多重复代码,重要的是,代码变得更加清晰易读了。此外,您还可以这样组织代码:

class FlatMap<T> {

    private init() { }

    static func request(api: (T) -> Target) -> (T) -> Observable<Result<JSON>> {
        return { object in
            return provider.request(api(object))
        }
    }

    static func request(api: (T) -> Target) -> (Result<T>) -> Observable<Result<JSON>> {
        return { result in
            switch result {
            case .value(let object):
                return request(api: api)(object)
            case let .error(error):
                return Observable.just(Result.error(error))
            }
        }
    }
    /// 过滤出 Result 中的 value
    static var filterValue: (Result<T>) -> Observable<T> {
        return { result in
            switch result {
            case .value(let object):
                return Observable.just(object)
            case .error:
                return Observable.empty()
            }
        }
    }
    /// 过滤出 Result 中的 error
    static var filterError: (Result<T>) -> Observable<ErrorProtocol> {
        return { result in
            switch result {
            case .value:
                return Observable.empty()
            case let .error(error):
                return Observable.just(error)
            }
        }
    }
}

这里我表示所有的方法都是用给 Rx 中 flatMap 操作的。

关于为什么 FlatMap 中会有 filter ,您可以参考这篇文章 用更 Swifty 的代码遍历数据。

这里还有一篇美团的 FRP 实践 iOS开发下的函数响应式编程 ,不论您是用 RAC 还是 Rx ,都值得看一看。

在实践中应用 RxSwift的更多相关文章

  1. ios – RxSwift:返回一个带有错误的新observable

    我有一个函数返回BoolObservable,具体取决于它是否正常.解决方法返回包含单个元素的可观察序列.相反,你应该使用这样的东西:Create方法从指定的subscribe方法实现创建一个可观察的序列.在你的情况下:Anonymousdisposable是在您想要中断的情况下调用的操作.假设您离开视图控制器或应用程序需要完成该服务,您不再需要再调用此请求.它非常适合视频上传或更大的内容.您可以

  2. ios – RxSwift:使用rx_refreshing进行uirefreshcontrol

    我正在使用UIRefreshControl变量绑定来重新加载数据.然而,它正在起作用,以下对我来说是错误的:1)我知道RXCocoa扩展中有一个rx_refreshing变量,但我无法让它在这个上下文中工作.2)我绑定答案两次.一旦我加载视图控制器,当UIRefreshControl刷新时再次加载.3)我检查UIRefreshControl是否令人耳目一新的部分看起来很尴尬.感觉它是否违背了使用反应的目的?

  3. ios – 将UIApplicationDelegate方法转换为RxSwift Observables

    得到打印然后我在RxCocoa的_RXDelegateProxy_类中得到以下崩溃:有谁知道问题可能是什么?或者有没有人成功实现过像rx_applicationDidBecomeActive这样的东西?

  4. 在实践中应用 RxSwift

    注:本文基于Swift3。Swift大会上有一场POP的分享,Demo地址RxPagination。摘要在上一篇在实践中应用RxSwift1-使用Result传递值中,我们解决了error的处理,但当我们处理一段很长的事件流时,会发现有很多不重要的代码,比如传递Error。在Rx中,map和flatMap是最常用的,我们添加一些小工具。以此表示这个方法是用在flatMap上的。关于为什么FlatMap中会有filter,您可以参考这篇文章用更Swifty的代码遍历数据。

  5. RxSwift使用教程

    前言RxSwift是Swift函数响应式编程的一个开源库,由Github的ReactiveX组织开发,维护。RxSwift的目的是让让数据/事件流和异步任务能够更方便的序列化处理,能够使用Swift进行响应式编程目前,RxSwift在Github上收到了5000+Star,600+fork。RxSwift的核心思想和这个类似。RxSwift的核心是想是Observablesequence,Observable表示可监听或者可观察,也就是说RxSwift的核心思想是可监听的序列。

  6. 初识RxSwift及使用教程 韩俊强的博客

    RxSwift是Swift函数响应式编程的一个开源库,由Github的ReactiveX组织开发、维护其他语言像C#,Java和JS也有,Rx.Net、RxJava、RxJSRxSwift的目的是让让数据/事件流和异步任务能够更方便的序列化处理,能够使用Swift进行响应式编程函数式响应编程?

  7. RxSwift使用教程大全 韩俊强的博客

    记录大多数ReactiveX的概念和操作符。我们还需要使用KVO来检测变量的值改变。Rx就是为解决这些问题而生的。Observable理解RxSwift的关键是理解Observable的概念。使用variable的好处是variable将不会显式的发送Error或者Completed。

  8. RxSwift 基础

    幸运的是,我们能够通过RxSwift优雅的处理异步代码。通过使用不可变代码定义异步处理输入,RxSwift以一种确定可组合的形式对事件做出响应。ObservableObservable类可以说是RxSwift整个框架的基石。补充值得注意的是,RxSwift并没有对客户端的应用架构作出硬性规定。这意味着,我们可以在已有项目中引入RxSwift进行响应式编程实践。当然已有框架中必定存在一个最适合RxSwift的,而它就是MVVM。总结作为系列开篇,本文介绍了RxSwift的一些基本理念和构成,更多相关的内容将

  9. RxSwift 之 Observable

    --more-->在前一篇基础之上,本文我们将会介绍RxSwift中的Observables部分。在RxSwift中Observable也被称为ObservableSequence、Sequence、Stream。Observable会以异步的方式不断的发射事件形成事件流,并且数据也会沿着事件流进行传播。Observable生命周期上图中的三个圆圈其实就是RxSwift中的next事件。订阅Observable在日常iOS编程中,通知模式可以说是使用频率相当高的一个设计模式。更为重要的是,在没有订阅者的时

  10. RxSwift 之变换操作

    可能刚开始接触RxSwift时候,你会觉得RxSwift非常难懂也不容易学。不过我相信认知读了前几篇文章后,你会深感RxSwift的强大。这篇文章将会继续介绍另一组非常重要的RxSwift操作:TransformingOperator。RxSwift中实现此功能最简单的方法就是通过toArray操作。而flatMap在RxSwift中的操作过程如下图:图中第一行O1、O2、O3表示三个类型实例,然后该类型实例有一个可观察属性value。总结本文简单了介绍了RxSwift中一些常见的变换操作。

随机推荐

  1. Swift UITextField,UITextView,UISegmentedControl,UISwitch

    下面我们通过一个demo来简单的实现下这些控件的功能.首先,我们拖将这几个控件拖到storyboard,并关联上相应的属性和动作.如图:关联上属性和动作后,看看实现的代码:

  2. swift UISlider,UIStepper

    我们用两个label来显示slider和stepper的值.再用张图片来显示改变stepper值的效果.首先,这三个控件需要全局变量声明如下然后,我们对所有的控件做个简单的布局:最后,当slider的值改变时,我们用一个label来显示值的变化,同样,用另一个label来显示stepper值的变化,并改变图片的大小:实现效果如下:

  3. preferredFontForTextStyle字体设置之更改

    即:

  4. Swift没有异常处理,遇到功能性错误怎么办?

    本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请发送邮件至dio@foxmail.com举报,一经查实,本站将立刻删除。

  5. 字典实战和UIKit初探

    ios中数组和字典的应用Applicationschedule类别子项类别名称优先级数据包contactsentertainment接触UIKit学习用Swift调用CocoaTouchimportUIKitletcolors=[]varbackView=UIView(frame:CGRectMake(0.0,0.0,320.0,CGFloat(colors.count*50)))backView

  6. swift语言IOS8开发战记21 Core Data2

    上一话中我们简单地介绍了一些coredata的基本知识,这一话我们通过编程来实现coredata的使用。还记得我们在coredata中定义的那个Model么,上面这段代码会加载这个Model。定义完方法之后,我们对coredata的准备都已经完成了。最后强调一点,coredata并不是数据库,它只是一个框架,协助我们进行数据库操作,它并不关心我们把数据存到哪里。

  7. swift语言IOS8开发战记22 Core Data3

    上一话我们定义了与coredata有关的变量和方法,做足了准备工作,这一话我们来试试能不能成功。首先打开上一话中生成的Info类,在其中引用头文件的地方添加一个@objc,不然后面会报错,我也不知道为什么。

  8. swift实战小程序1天气预报

    在有一定swift基础的情况下,让我们来做一些小程序练练手,今天来试试做一个简单地天气预报。然后在btnpressed方法中依旧增加loadWeather方法.在loadWeather方法中加上信息的显示语句:运行一下看看效果,如图:虽然显示出来了,但是我们的text是可编辑状态的,在storyboard中勾选Editable,再次运行:大功告成,而且现在每次单击按钮,就会重新请求天气情况,大家也来试试吧。

  9. 【iOS学习01】swift ? and !  的学习

    如果不初始化就会报错。

  10. swift语言IOS8开发战记23 Core Data4

    接着我们需要把我们的Rest类变成一个被coredata管理的类,点开Rest类,作如下修改:关键字@NSManaged的作用是与实体中对应的属性通信,BinaryData对应的类型是NSData,CoreData没有布尔属性,只能用0和1来区分。进行如下操作,输入类名:建立好之后因为我们之前写的代码有些地方并不适用于coredata,所以编译器会报错,现在来一一解决。

返回
顶部