响应式编程综合演练


介绍

此篇文章仅献给想学习响应式编程的初学者。
此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
//校验请求方法声明
func checkUserNameAvaliable(userName: String) -> Observable
//控制器中的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进行了封装(可以理解讲一个普通值变为了一个上下文中的值,方便链式调用),在该方法中又调用了校验请求的方法;
  3. 在校验请求的方法中,实际发起了网路请求,并对网络请求结果进行处理,返回Observable这样的值(可以理解为将一个普通的bool类型的值封装为了一个上下文中的值),在校验方法中又将该
    Observable转化为了Observable
  4. 回到viewmodel中,validService.validationUserName()这个方法就是拿到上一步的处理结果Observable,而driver是特殊的observable,只要满足条件就可以转化为driver,但由于observable可以出现错误终止信号,而driver不可以,所以driver捕获该错误将之转化为发送其它正常信号;
  5. 控制器中viewModel.validateUserName.drive拿到了之前的处理结果Observable,接下来就是如何将结果具体绑定到UI上。
处理结果与UI的绑定
extension Reactive where Base: UILabel {

    var validationResult: UIBindingObserver {
        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 = default, 
requestClosure: @escaping (Moya.Endpoint, @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),还可以自定义实现插件,只需实现插件协议
  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的实现原理及全方位讲解使用。

看~灰机灰过来了~灰机又灰过去了~

你可能感兴趣的:(响应式编程综合演练)