前言
目前高移动端应用的要求已经越来越高, 主要体现在:
- 越来越复杂的用户可操作页面;
 - 多个页面一起承载繁琐的业务;
 - 多个状态需要实时反映到应用界面.
 
这种背景下正是MVVM模式施展的时候, 而MVVM模式下的好搭档无疑要提到响应式框架.以下把个人在工作中对RxSwift&MVVM实践的经验记录一下.
数据流中心模块: Service
我在项目中构建了一个Service模块, 作为数据流的中心, 这个模块的作用有以下几点:
- 
    
单例模式, 项目共享数据与状态.
 - 
    
提供业务/功能相关的函数, 内部调用下一层异步函数, 例如 Network API, Location API, 第三方SDK API, Database API等操作.
 - 
    
以属性的形式提供全局可观察事件, 例如 User登录状态变更, Location 更新.
 
Service模块之Observable
在Service模块中, 大部分函数的返回值和属性的类型都是Observable.
Observable是Rx框架中的最基本可观察类型, 其中有一个需要用到的概念——广播型.
广播类型和非广播类型的区别在于, 广播类型可以同时多次subsribe, 这个特性正好契合全局可观察事件. 因此Service模块中提供的Observable属性都应该是广播类型的. 而Rx框架里面的广播类型Observable, 对应的就是PublishSubject和ReplaySubject.
而相对地, 函数返回的对象则只需要用非广播类型, 因为实际情况调用函数的回调都只是调用者自己需要subscribe. 如果这个回调的结果有必要转化成广播类型, 则应该通过调用者自己转换.
Service模块之属性
既然已经可以确定, Service属性中的Observable需要用广播类型, 那如何选择PublishSubject和ReplaySubject?
先看两个类型的描述:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/// 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收到一些莫名的数据更新. 对此, 人为约束:
- 有副作用的函数, 会导致
Service持有的数据改变, 或者导致Service的可观察对象发送数据的, 使用以下命名:- 会返回数组数据的函数, 
reloadxxx表示重新刷新,loadMorexxx表示加载更多. - 返回
hash数据/Model的函数, 使用updatexxx表示更新. 
 - 会返回数组数据的函数, 
 - 没有副作用的函数:
    
- 请求数据的, 无论返回什么类型, 使用
fetchxxx表示请求. - 执行某类操作, 使用
performxxx. 例如登录, 命名应该是performSignin. 
 - 请求数据的, 无论返回什么类型, 使用
 
ViewModel和View
MVVM有别于MVC主要在于ViewModel模块, 设想中它的功能有以下:
- 存储
View/ViewController层要直接显示的数据, 注意不只是持有Model. - 针对对应的
View/ViewController提供UI交互的输入口(Tableview, TextField, Button, Segmented等操作). - 内部处理UI的input数据, 调用对应
Service的业务函数, 并提供输出口绑定到View/ViewController上. - 映射
Service模块的一些可观察事件, 输出给View/ViewControllersubscribe. 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
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(有点绕, 出图表达).

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

示例代码
持续更新...