理解RxSwift和MVVM

570 阅读6分钟

一、RxSwift (Rx)

Rx是一个关于函数响应式编程的框架,用来解决代理通知回调传递数据多个对象之间状态改变引起的数据流动等不符合模块之间高内聚低耦合思想的问题,可以将数据流动按照链式调用的顺序进行传递,达到业务逻辑分离的目的。

Redux的介绍

先了解一下redux来帮助理清Rx的架构思路,redux是帮助统一管理state,通过action来触发state改变的框架,它的基本架构图如下:

  • reducer函数是接受oldState并进行一定处理返回newState的纯函数,它在store一开始构建的时候就进行赋值,其作用是用来监听state的改变。
  • subscribe是订阅的意思,在该回调函数中程序员可以拿到newState并进行自己定义的业务逻辑处理,订阅者就是store,也可以理解为被观察者 。
  • dispatch在触发state发生改变或者说是某个action发生时进行调用,reducer内部通过判断是哪个action来返回相应的newState,并通过subscribe进行回调传递,这样程序员就可以拿到新的值进行处理。

Rx的理解

返回来说Rx,这是一个基本的使用方式

scrollView.rx.contentOffset
            .subscribe(onNext: { contentOffset in
               print("contentOffset: \(contentOffset)")
             })
            .disposed(by: disposeBag)

在Rx中数据的流动方式为:

数据产生源——>某个action被触发或者某个state发生改变——>进行业务逻辑处理——>数据接收者

数据按照这个过程进行流动,经过业务处理后被赋值给数据接受者,从而达到我们想要的结果。对于上面的代码:

  • scrollView数据产生源,contentOffset在这段代码中是真正的被观察者和数据产生源
  • onNext回调中是业务逻辑处理的代码,数据接收者这里没有出现,我可以把拿到的数据赋值给某个属性某个参数等等。

redux进行比较发现有这相似之处:

  • scrollView就是storecontentOffset就是某个具体的state,发生滚动是action,发生滚动时Rx内部进行了监听并触发了类似dispatch的函数使得subcribe中我们自己的业务代码被执行。

  • 这就很好理解Rx中的Observable(可观察序列),也就是被观察者 、数据产生源 ,由它去注册监听(也就是执行subcribe),而Observer(观察者)reducer(这是一个抽象的称呼,代表将oldState经过一系列转换得到newState的纯函数)是Rx内部帮我们做好了并隐藏起来,这对使用来说非常方便,但对理解内部逻辑和自定义ObservableObserver是比较困难的。

下面是自定义Observable的一段代码:

let json: Observable<JSON> = Observable.create { (observer) -> Disposable in
    let task = URLSession.shared.dataTask(with: ...) { data, _, error in
        guard error == nil else {
            observer.onError(error!) // 发生错误需要进行error的回调
            return
        }
        guard let data = data,
            ···json序列化转换处理
        else {
            observer.onError(DataError.cantParseJSON) // 转换失败进行error回调
            return
        }
        observer.onNext(jsonObject) // 得到结果,也就是oldState——》newState成功,将结                                        果回调出去,进行业务处理
        observer.onCompleted() // 完成的回调
    }
    task.resume()
    return Disposables.create { task.cancel() } // 资源回收是取消task
}
// 
// 对于上面的回调函数具体实现,拿到数据进行业务处理
json
    .subscribe(onNext: { json in
        print("取得 json 成功: \(json)")
    }, onError: { error in
        print("取得 json 失败 Error: \(error.localizedDescription)")
    }, onCompleted: {
        print("取得 json 任务成功完成")
    })
    .disposed(by: disposeBag)

根据上面的代码可以对我们的理解进行进一步验证:

  • 可以看到create方法的参数是一个闭包,内部有个已经定义好的observer,拿到初始数据oldState(在上段代码中是请求回来的data,也可以是scoreView开始滚动时的contentOffset
  • 经过某个action后(这里action比较抽象,可以理解为点击、滚动、序列化 等等)进行数据转换处理得到newState(在上段代码中是json序列化后的jsonObject,也可以是scoreView停止滚动后的contentOffset),这就相当于redux中的reducer部分,也就是隐藏起来的观察者监听值改变的部分onNext捕获newState并执行相应的回调(一般来说就是程序员自定义的业务逻辑代码),通过onErroronCompleted监听出错或者完成状态并执行回调。
  • 不像传统的KVO需要进行addObserver来指定一个观察者,这里每个Observable都有一一对应的Observer,且创建时就隐藏在内部。理解了这些部分就可以正常使用Rx了。

如果是监听UI的改变,有一些更简便的用法,通过Binder,只会处理next事件,并且更新UI 的操作需要在主线程上执行。例如view隐藏的isHidden属性通常是数据的接收者 ,因此Rx帮我们做好了封装:

// 对UIview中某个熟悉的扩展,可以用来被绑定
extension Reactive where Base: UIView {
  public var isHidden: Binder<Bool> {
      return Binder(self.base) { view, hidden in
          view.isHidden = hidden
      }
  }
}
// passwordOutlet是文本输入框
passwordOutlet.rx.text.orEmpty
    .map { $0.characters.count >= minimalPasswordLength }
    .bind(to: passwordValidOutlet.rx.isHidden) // isHidden是数据的接收者
    .disposed(by: disposeBag)

除了上面介绍的之外还有多种Subject,是ObservableObserver的结合体,也就是既可以注册监听(调用subscribe),也可以当观察者调用onNext发送数据。另外有许多操作符可以对数据进行多种转换,具体参考官网手册。

二、MVVM:

  • 先了解MVC,下面是它的架构图:
    可以看出ViewModel事实上是没有交互的,由Controller负责ModelView之间的交互,交互越多,Controller就越臃肿。目前对MVC架构划分是Model作为数据管理者,View作为数据展示者,Controller进行业务处理和数据传递。
摘自Casa Taloyum的划分
M应该做的事: 
1.给ViewController提供数据 
2.给ViewController存储数据提供接口 
3.提供经过抽象的业务基本组件,供Controller调度 
C应该做的事: 
1.管理View Container的生命周期 
2.负责生成所有的View实例,并放入View Container 
3.监听来自View与业务有关的事件,通过与Model的合作,来完成对应事件的业务。  
V应该做的事: 
1.响应与业务无关的事件,并因此引发动画效果,点击反馈(如果合适的话,尽量还是放在View去做)等。 2.界面元素表达
  • MVVM架构图:
    抽出了ViewModel层来负责数据与视图的交互部分,Controller仅协调各个部分的绑定关系以及必要的逻辑处理。结合Rx进行双向绑定: 1、view当做Observable,通过subscribe中的回调onNext将数据传递给viewmodel中的具体业务处理函数 2、viewModel当做Observable,通过subscribe中的回调onNext将处理后的数据传递给model,当然也可以通过将model持有为属性来更新数据 所以viewmodel最好是一个Subject类型,方便后续的使用。

-END-