iOS学习

RxSwift应用MVVM

2019-04-01  本文已影响8人  奥利奥_2aff

目前高移动端应用的要求已经越来越高, 主要体现在:

  1. 越来越复杂的用户可操作页面;
  2. 多个页面一起承载繁琐的业务;
  3. 多个状态需要实时反映到应用界面.

这种背景下正是MVVM模式施展的时候, 而MVVM模式下的好搭档无疑要提到响应式框架.以下把个人在工作中对RxSwift&MVVM实践的经验记录一下.

数据流中心模块: Service

我在项目中构建了一个Service模块, 作为数据流的中心, 这个模块的作用有以下几点:

Service模块之Observable

在Service模块中, 大部分函数的返回值和属性的类型都是Observable.

Observable是Rx框架中的最基本可观察类型, 其中有一个需要用到的概念——广播型.

广播类型和非广播类型的区别在于, 广播类型可以同时多次subsribe, 这个特性正好契合全局可观察事件. 因此Service模块中提供的Observable属性都应该是广播类型的. 而Rx框架里面的广播类型Observable, 对应的就是PublishSubjectReplaySubject.

而相对地, 函数返回的对象则只需要用非广播类型, 因为实际情况调用函数的回调都只是调用者自己需要subscribe. 如果这个回调的结果有必要转化成广播类型, 则应该通过调用者自己转换.

Service模块之属性

既然已经可以确定, Service属性中的Observable需要用广播类型, 那如何选择PublishSubjectReplaySubject?

先看两个类型的描述:

/// Represents an object that is both an observable sequence as well as an observer.
///
/// Each notification is broadcasted to all subscribed observers.


public final class PublishSubject<Element>
    : Observable<Element>
    , SubjectType
    , Cancelable
    , ObserverType
    , SynchronizedUnsubscribeType {
    ...
    ...
}

/// Represents an object that is both an observable sequence as well as an observer.
///
/// Each notification is broadcasted to all subscribed and future observers, subject to buffer trimming policies.

public class ReplaySubject<Element>
    : Observable<Element>
    , SubjectType
    , ObserverType
    , Disposable {
    ...
    ...
}

从注释上面可以了解到, ReplaySubject的不同在于, 它的数据会发送给将来订阅它的监听者, 也就是说, ReplaySubject的数据可以追溯.

可以得出ReplaySubject的使用策略:

当数据发送的时机早于数据被正式使用的时候, 我们用ReplaySubject, 这样可以避免我们丢失已经获得的数据.

Service模块之函数

在设想中, 我希望Service是MVVM中的ViewModel的辅助, ViewModel调用Service提供的函数, 而Service应该帮ViewModel处理好与UI无关的业务逻辑. 所以Service的函数应该有大量异步串行/并行的操作, 并且返回Observable类型.

剩下需要注意的就是, 函数的副作用. 这一点非常值得注意, 因为稍不注意副作用的管理, 就会导致ViewModel收到一些莫名的数据更新. 对此, 人为约束:

ViewModel和View

MVVM有别于MVC主要在于ViewModel模块, 设想中它的功能有以下:

class SignViewControllerViewModel {
    // input
    var usernameInput: BehaviorRelay<String> = BehaviorRelay(value: "")
    var passwordInput: BehaviorRelay<String> = BehaviorRelay(value: "")
    // output
    var usernameInputEnable: Observable<Bool>
    var passwordInputEnable: Observable<Bool>
    var submitEnable: BehaviorRelay<Bool> = BehaviorRelay(value: true)
    var submitTitle: BehaviorRelay<String> = BehaviorRelay(value: "Submit")
    var indicatorHidden: Observable<Bool>
    var stateHidden: Observable<Bool>
    var state: Observable<String> {
        return _internalState.asObservable()
    }
    ...
    ...
    
    init(disposeBag: DisposeBag!) {
        ...
    }
    
    func handleClickSubmit() {
        ...
    }
}

MVVM中的V是视图层, 在项目中视图层主要其实是ViewController和各种View的子类, 并不是单独指View.

View/ViewController要做的就是做布局, 以及调用ViewModel:

class SignViewController: UIViewController {
    // 懒加载, 传入disposeBag
    var viewModel: SignViewControllerViewModel
    // UI
    @IBOutlet weak var usernameTf: UITextField!
    @IBOutlet weak var passwordTf: UITextField!
    @IBOutlet weak var submitButton: UIButton!
    @IBOutlet weak var stateLabel: UILabel!
    @IBOutlet weak var indicatorView: UIActivityIndicatorView!
    
    // deinit之后释放subscribtions
    let disposeBag: DisposeBag = DisposeBag()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        ...
        ...
        self.bindObservables()
    }
    
    func bindObservables() {
        // 数据输入 viewModel
        self.usernameTf.rx.text.orEmpty
            .bind(to: self.viewModel.usernameInput)
            .disposed(by: self.disposeBag)
        
        self.passwordTf.rx.text.orEmpty
            .bind(to: self.viewModel.passwordInput)
            .disposed(by: self.disposeBag)
        
        self.submitButton.rx.tap
            .subscribe(onNext: {_ in self.viewModel.handleClickSubmit()})
            .disposed(by: self.disposeBag)
        
        
        // viewModel 数据输出
        self.viewModel.submitEnable
            .bind(to: self.submitButton.rx.isEnabled)
            .disposed(by: self.disposeBag)
        
        self.viewModel.usernameInputEnable
            .bind(to: self.usernameTf.rx.isEnabled)
            .disposed(by: self.disposeBag)
        
        self.viewModel.passwordInputEnable
            .bind(to: self.passwordTf.rx.isEnabled)
            .disposed(by: self.disposeBag)
        
        self.viewModel.submitTitle
            .bind(to: self.submitButton.rx.title())
            .disposed(by: self.disposeBag)
        
        self.viewModel.state
            .bind(to: self.stateLabel.rx.text)
            .disposed(by: self.disposeBag)
        
        self.viewModel.indicatorHidden
            .bind(to: self.indicatorView.rx.isHidden)
            .disposed(by: self.disposeBag)
        
        self.viewModel.indicatorHidden
            .map({!$0})
            .bind(to: self.indicatorView.rx.isAnimating)
            .disposed(by: self.disposeBag)
        
        self.viewModel.stateHidden
            .bind(to: self.stateLabel.rx.isHidden)
            .disposed(by: self.disposeBag)
    }
}

在理想情况下, View/ViewController只面对ViewModel, 而ViewModel面对的就是Service, Model, View/ViewController

注意: 因为ViewController中必定存在若干个View, 而View是可以复用的, 甚至ViewController也是可以作为childViewController被复用, 所以ViewController的应该有一个对应的ViewModel, 而ViewController的ViewModel有可能需要持有若干个View的ViewModel(有点绕, 出图表达).

view_model_structure.png

数据: Model

Model在MVVM中是最轻的一个模块, 除了保存数据的值, 它什么都不需要做, 所见即所有.

最终的MVVM结构

mvvm_structure.png

[示例代码](https://github.com/p36348/blog_posts_demos/tree/master/RxSwiftMVVMDemo

)


持续更新...

上一篇下一篇

猜你喜欢

热点阅读