响应式编程综合演练


介绍

此篇文章仅献给想学习响应式编程的初学者。
demo是一个完整的登录功能的实现,包含第三方:

  • RxSwift
  • RxCocoa
  • Moya
  • SwiftJson
  • ObjectMapper
  • Alamofire
    实现了登录页面的输入校验,http设置及请求,模型转换,缓存,自定义错误处理插件

关于请求缓存的举例在登录中不太恰当,只是用来说明如何实现

demo结构介绍

  • DMUser: 用户模型
  • DMLoginViewController: 登录控制器
  • DMLoginResult:登录状态(枚举)
  • DMLoginViewModel:数据业务处理
  • DMLoginProtocols: 登录所用到的接口(协议)
  • DMLoginService:服务(具体类,实现了所定义的协议接口)
  • DMLoginHttp:遵从Moya的TargetType,请求定义
  • DMDeployProvider:提供请求的额外配置,包含:request和session manager
  • DMMoyaHttpErrorHandlePlugin: 自定义插件,可用来统一处理错误
  • DMCache: 缓存,并扩展RxMoya支持缓存

数据校验

关于登录的输入校验,直接使用RXSwift官方demo内的GitHubSignup的UsingDriver > 2,看过的可以跳过该章节。

首先介绍下driver,
关于driver和observable的区别,官方是这样说的:
* Can't error out (不能错误终止信号)
* Observe on main scheduler (在主线程观察)
* Subscribe on main scheduler (在主线程订阅)
* Sharing side effects (分享副作用,shareReplay())

官方示例中 UsingDriver > 2 是UsingVanillaObservables > 1的优化版本

介绍下官方示例

官方示例采用的是MVVM + OOP的方案实现。
在控制器中,将输入源(文本框,按钮等事件)和实现协议的服务类实例传递给viewmodel中,然后在viewmodel中去观察输入源的变化并处理,在控制器中去订阅这些信号(处理过的信号)。流程大体是这样的,具体参考代码,这里重点介绍两处:

关于数据的传递,也可以称为数据的流向。

由于swift闭包的特性,闭包内参数未显示声明参数的类型,或者直接省略了参数列表,而已$0, $1代替,造成了理解上的不方便。

//viewmodel中校验用户名
validateUserName = input.userName.flatMapLatest {
            return validService.validationUserName($0)
              .asDriver(onErrorJustReturn: .failed(message: "链接服务失败"))
 }
//校验方法声明
func validationUserName(_ usernName: String) -> Observable<ValidationResult>
//校验请求方法声明
func checkUserNameAvaliable(userName: String) -> Observable<Bool>
//控制器中的UI绑定
viewModel.validateUserName
          .drive(userNameValidLabel.rx.validationResult)
          .addDisposableTo(disposeBag)

在这一个流程中,数据是如何传递与使用的呢?

初学者刚开始接触可能难免困惑,这一点要提及下函数式编程几个概念(Functor,Applicative,Monad),有兴趣的可以阅读下,有助于理解。
个人理解:将一个值a封装为上下文值A,运用一个处理普通值但返回上下文中的值的函数B,得到另一个上下文中的值C,然后不断传递再处理,就形成了响应式编程实现的这种独特写法,还可以参考:
雷纯锋的技术博客1
雷纯锋的技术博客2

解析上方代码片段:

  1. 在viewmodel中去观察文本框的变化,flatMapLatest是获取最新的舍弃旧的,该闭包内省略的参数是文本框的文本,即textField.text <=> $0,将文本传递给校验方法;
  2. 在校验方法中,完成对该文本的校验,并返回一个可观察的对象(不知道怎么称呼,就以单词意思解释了)Observable<ValidationResult>,是对ValidationResult进行了封装(可以理解讲一个普通值变为了一个上下文中的值,方便链式调用),在该方法中又调用了校验请求的方法;
  3. 在校验请求的方法中,实际发起了网路请求,并对网络请求结果进行处理,返回Observable<Bool>这样的值(可以理解为将一个普通的bool类型的值封装为了一个上下文中的值),在校验方法中又将该
    Observable<Bool>转化为了Observable<ValidationResult>;
  4. 回到viewmodel中,validService.validationUserName()这个方法就是拿到上一步的处理结果Observable<ValidationResult>,而driver是特殊的observable,只要满足条件就可以转化为driver,但由于observable可以出现错误终止信号,而driver不可以,所以driver捕获该错误将之转化为发送其它正常信号;
  5. 控制器中viewModel.validateUserName.drive拿到了之前的处理结果Observable<ValidationResult>,接下来就是如何将结果具体绑定到UI上。
处理结果与UI的绑定
extension Reactive where Base: UILabel {

    var validationResult: UIBindingObserver<Base, ValidationResult> {
        return UIBindingObserver(UIElement: base) { label, result in
            label.textColor = result.textColor
            label.text = result.description
        }
    }
}

对于RxCocoa支持的可以直接使用,但不支持的或者需要自定义的就要实现这个方法,此处对于Reactive进行扩展,实现了label对于ValidationResult的自定义展示。

网络请求

网络请求采用的是比Alamofire更高一层的抽象,Moya,并且也支持了RxSwift。
基本用法不多介绍,参照demo(DMLoginHttp,DMLoginService)或其它博客,这里介绍下对它的其它的一些自定义。
首先介绍下它的初始化参数,都有默认值,可以省略,但业务总是复杂多变的,需要我们的定制,所以先了解下它的各项参数含义以便配置。可见官方文档 Providers.md

public init(
endpointClosure: @escaping (Target) -> Moya.Endpoint<Target> = default, 
requestClosure: @escaping (Moya.Endpoint<Target>, @escaping Moya.MoyaProvider.RequestResultClosure) -> Swift.Void = default,
stubClosure: @escaping (Target) -> Moya.StubBehavior = default, 
manager: Moya.Manager = default,
plugins: [PluginType] = default,
trackInflights: Bool = default)
  1. Moya的方式是:endPoint -> request,所以在此处可以对请求进行再设置,包含url,header等;
  2. 接收之前的 endPoint生成request的实例,在此可对request再设置;
  3. 用来控制模拟请求的,即返回的是本地数据;
  4. 用来生成Alamofire的sessionManager实例,此处可进行超时时间,https自签名证书的设置等,假如你之前直接使用Alamofire的manager的设置都可以移植到此处设置,;
  5. 插件设置,本身提供了三种插件(NetworkActivityPlugin,CredentialsPlugin,NetworkLoggerPlugin),还可以自定义实现插件,只需实现插件协议<PluginType>
  6. trackInflights ??(额暂时不知道,有知道的可以告诉我下)

此demo我自定义了一下相关的:

  • DMDeployProvider:定义了endPoint和manager
  • DMMoyaHttpErrorHandlePlugin: 自定义插件,错误统一处理

缓存

由于Moya未提供离线缓存,因此需要自己扩展Moya实现缓存。此例子中缓存是以字典形式存在内存中的,可替换为真正的缓存框架,此处仅仅是为了展示如何扩展缓存。

DMCache.swift中,
定义了缓存策略:DMCacheType
缓存类(假的,示范用): DMCache
扩展:是Moya支持缓存,tryCache()

核心代码:
return Observable.create({[weak self, weak cache]  observe -> Disposable in
            
            switch cacheType {
            case .onlyCache:
                print("使用缓存!!!")
                if let response = cache?.getValue(forKey: identifier) {
                    observe.onNext(response)
                }
                observe.onCompleted()
            case .cacheThenRequest:
                print("先使用缓存再请求数据!!!")
                if let response = cache?.getValue(forKey: identifier) {
                    observe.onNext(response)
                }
                fallthrough
            case .onlyRequest:
                print("请求数据!!!")
                task = self?.request(target) { result in
                    switch result {
                    case let .success(response):
                        observe.onNext(response)
                        observe.onCompleted()
                        cache?.storeValue(response, forKey: identifier)
                    case let .failure(error):
                        observe.onError(error)
                    }
                }
            }
            return Disposables.create {
                task?.cancel()
            }
        })

此处仅需要依据不同的缓存策略,包装实现即可。
注意fallthrough关键字,小心坑~~ :)

模型

最后拿到数据,使用ObjectMapper进行JSON转模型即可,不多介绍


总结:此demo示例了如何用响应式编程将众多功能结合在一起,着重介绍了RxSwift和Moya的结合使用,后续会重点关注RxSwift的实现原理及全方位讲解使用。

看~灰机灰过来了~灰机又灰过去了~
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,497评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,074评论 25 707
  • 文章摘自Moya官方文档 Targets Moya的使用始于定义一个target——典型的是定义一个符合Targe...
    Jt_Self阅读 16,077评论 0 27
  • 读书的时光就像迷失在天堂, 摧毁这梦幻的是闹钟的叫床, 天知道书包和文具放在哪里, 墙壁贴满了球星明星的海报。 校...
    诗的影子阅读 255评论 0 0
  • 你试过在跑步的时候写作吗?我现在正在干这件事情 回到家时已经晚上10点半了,距晚上12点,还有一个半小时,我需要,...
    野蛮实践阅读 849评论 0 1