MVVM

MVVMModel-View-ViewModel 的简写。如果你已经对 MVC 非常熟悉了,那么上手 MVVM 也是非常容易的。


MVC

MVVM - 图1

MVCModel-View-Controller 的简写。MVC 主要有三层:

  • Model 数据层,读写数据,保存 App 状态
  • View 页面层,和用户交互,向用户显示页面,反馈用户行为
  • ViewController 逻辑层,更新数据,或者页面,处理业务逻辑

MVC 可以帮助你很好的将数据,页面,逻辑的代码分离开来。使得每一层相对独立。这样你就能够将一些可复用的功能抽离出来,化繁为简。只不过,一旦 App 的交互变复杂,你就会发现 ViewController 将变得十分臃肿。大量代码被添加到控制器中,使得控制器负担过重。此时,你就需要想办法将控制器里面的代码进一步地分离出来,对 APP 进行重新分层。而 MVVM 就是一种进阶的分层方案。


MVVM

MVVM - 图2

MVVMMVC 十分相识。只不过他的分层更加详细:

  • Model 数据层,读写数据,保存 App 状态
  • View 页面层,提供用户输入行为,并且显示输出状态
  • ViewModel 逻辑层,它将用户输入行为,转换成输出状态
  • ViewController 主要负责数据绑定

没错,ViewModel 现在是逻辑层,而控制器只需要负责数据绑定。如此一来控制器的负担就减轻了许多。并且 ViewModel控制器以及页面相独立。那么,你就可以跨平台使用它。你也可以很容易地测试它。


示例

这里我们将用 MVVM 来重构输入验证

MVVM - 图3

重构前:

  1. class SimpleValidationViewController : ViewController {
  2. ...
  3. override func viewDidLoad() {
  4. super.viewDidLoad()
  5. ...
  6. let usernameValid = usernameOutlet.rx.text.orEmpty
  7. .map { $0.characters.count >= minimalUsernameLength }
  8. .share(replay: 1)
  9. let passwordValid = passwordOutlet.rx.text.orEmpty
  10. .map { $0.characters.count >= minimalPasswordLength }
  11. .share(replay: 1)
  12. let everythingValid = Observable.combineLatest(
  13. usernameValid,
  14. passwordValid
  15. ) { $0 && $1 }
  16. .share(replay: 1)
  17. usernameValid
  18. .bind(to: passwordOutlet.rx.isEnabled)
  19. .disposed(by: disposeBag)
  20. usernameValid
  21. .bind(to: usernameValidOutlet.rx.isHidden)
  22. .disposed(by: disposeBag)
  23. passwordValid
  24. .bind(to: passwordValidOutlet.rx.isHidden)
  25. .disposed(by: disposeBag)
  26. everythingValid
  27. .bind(to: doSomethingOutlet.rx.isEnabled)
  28. .disposed(by: disposeBag)
  29. doSomethingOutlet.rx.tap
  30. .subscribe(onNext: { [weak self] in self?.showAlert() })
  31. .disposed(by: disposeBag)
  32. }
  33. ...
  34. }

ViewModel

MVVM - 图4

ViewModel 将用户输入行为,转换成输出的状态:

  1. class SimpleValidationViewModel {
  2. // 输出
  3. let usernameValid: Observable<Bool>
  4. let passwordValid: Observable<Bool>
  5. let everythingValid: Observable<Bool>
  6. // 输入 -> 输出
  7. init(
  8. username: Observable<String>,
  9. password: Observable<String>
  10. ) {
  11. usernameValid = username
  12. .map { $0.characters.count >= minimalUsernameLength }
  13. .share(replay: 1)
  14. passwordValid = password
  15. .map { $0.characters.count >= minimalPasswordLength }
  16. .share(replay: 1)
  17. everythingValid = Observable.combineLatest(usernameValid, passwordValid) { $0 && $1 }
  18. .share(replay: 1)
  19. }
  20. }

输入:

  • username 输入的用户名
  • password 输入的密码

输出:

  • usernameValid 用户名是否有效
  • passwordValid 密码是否有效
  • everythingValid 所有输入是否有效

init 方法内部,将输入转换为输出。


ViewController

ViewController 主要负责数据绑定:

  1. class SimpleValidationViewController : ViewController {
  2. ...
  3. private var viewModel: SimpleValidationViewModel!
  4. override func viewDidLoad() {
  5. super.viewDidLoad()
  6. ...
  7. viewModel = SimpleValidationViewModel(
  8. username: usernameOutlet.rx.text.orEmpty.asObservable(),
  9. password: passwordOutlet.rx.text.orEmpty.asObservable()
  10. )
  11. viewModel.usernameValid
  12. .bind(to: passwordOutlet.rx.isEnabled)
  13. .disposed(by: disposeBag)
  14. viewModel.usernameValid
  15. .bind(to: usernameValidOutlet.rx.isHidden)
  16. .disposed(by: disposeBag)
  17. viewModel.passwordValid
  18. .bind(to: passwordValidOutlet.rx.isHidden)
  19. .disposed(by: disposeBag)
  20. viewModel.everythingValid
  21. .bind(to: doSomethingOutlet.rx.isEnabled)
  22. .disposed(by: disposeBag)
  23. doSomethingOutlet.rx.tap
  24. .subscribe(onNext: { [weak self] in self?.showAlert() })
  25. .disposed(by: disposeBag)
  26. }
  27. ...
  28. }

输入:

  • username 将输入的用户名传入 ViewModel
  • password 将输入的密码传入 ViewModel

输出:

  • usernameValid 用用户名是否有效,来控制提示语是否隐藏,密码输入框是否可用
  • passwordValid 用密码是否有效,来控制提示语是否隐藏
  • everythingValid 用两者是否同时有效,来控制按钮是否可点击

当 App 的交互变复杂时,你仍然可以保持控制器结构清晰。这样可以大大的提升代码可读性。将来代码维护起来也就会容易许多了。


示例

下一节将用 Github Signup 来演示如何使用 MVVM

注意⚠️:这里介绍的 MVVM 并不是严格意义上的 MVVM。但我们通常都管它叫 MVVM,而且它配合 RxSwift 使用起来非常方便。如需了解什么是严格意义上的 MVVM,请参考微软The MVVM Pattern