可复用的 View 和 Model
先来简单回顾一下什么是 MVC:
Controller负责业务处理、网络处理、本地存储等,难复用;
View负责展示,与Model分离不可见,可复用;
Model负责数据,与View分离不可见,可复用;
那么 MVVM 呢:
ViewModel 可以负责业务处理、网络处理、本地存储、包装 Model 给 View 等等,复用困难;
View负责展示,与Model分离不可见,可复用;
Model负责数据,与View分离不可见,可复用;
Controller被看做一个重度的View,复用困难。
在这2种模式中,易于复用的只有 View 和 Model。
双向绑定
我们都知道 MVVM 需要进行双向绑定,这也是 MVVM 的核心竞争力。
那么什么是双向绑定?就是数据流的2个方向:
View >>> ViewModel >>> Model
View <<< ViewModel <<< Model
因为 ViewModel 需要处理业务或数据逻辑,并且我们不需要复用它,所以可以在 ViewModel 中直接持有 Model 进行绑定,这样他们之间双向的数据交互很容易实现。
接下来,View 和 ViewModel 如何绑定?
绑定 View 与 ViewModel
有一说法是 View 持有 ViewModel 进行绑定,这样的View可以复用吗?我认为是极难的。ViewModel里面是处理业务、网络请求等等逻辑,如果我复用了这个View,也就意味着耦合了这个ViewModel。
试想一下,
场景1,正常情况下,业务逻辑在ViewModel中,ViewModel绑定到View,View持有ViewModel。
场景2,需要复用View,此场景下的业务逻辑在 ViewModel-1 中,这时如何建立 View 和 ViewModel-1 的绑定?在View中再添加一个 ViewModel-1 属性。
场景3,….,在View中再添加一个 ViewModel-2 属性。
场景4,….,在View中再添加一个 ViewModel-3 属性。
…
这样的View能复用吗?肯定不能,会疯掉的!!
而如果将绑定代码写在 ViewModel 中,当然是可以的,但是这样一来会给单元测试逻辑时耦合View,所以也不是一个好办法。
那么写在Controller中?
Cool!首先,Controller很难进行单元测试,并且它本身就需要 ViewModel 的业务入口,需要 View 展示视图,也就是说 Controller 会持有 ViewModel 和 View;其次,Controller 本生也难以被复用,所以 Controller 中的代码一般不会考虑其复用性;所以绑定代码写在 Controller 中是最合适的地方。
示例
关于绑定的代码,你可以选择 RAC,RxSwift,KVO或者通知、block,都可以。以下我使用闭包(Block)的方式写了一个小示例,选择 block 是因为其他方式代码量太多…
Model
class Model: NSObject {
var num: Int?
}
复制代码
View
class View: UIView {
let numButton = UIButton()
var numClickBlock: (() -> Void)? // block
override init(frame: CGRect) {
super.init(frame: frame)
numButton.backgroundColor = .red
numButton.addTarget(self, action: #selector(numClick), for: .touchUpInside)
numButton.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
addSubview(numButton)
// ... other UI coding
}
@objc func numClick() {
self.numClickBlock?() // 暴露给外界的事件
}
// ...
}
复制代码
ViewModel
class ViewModel: NSObject {
private let model = Model()
var numStr: String = ""
var updatedBlock: ((String, String) -> ())?
override init() {
super.init()
model.num = 10; // 初始值
numStr = "10"
}
func addAction() {
model.num! += 1
numStr = String(model.num!);
updatedBlock?("add", numStr) // 通知外界数据更新
}
func subAction() {
model.num! -= 1;
numStr = String(model.num!);
updatedBlock?("sub", numStr)
}
}
复制代码
最后,我们来看看 ViewController 中的代码:
class ViewController: UIViewController {
var viewModel = ViewModel()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
let view = View(frame: CGRect(x: 20, y: 88, width: 100, height: 100))
self.view.addSubview(view)
bind(view: view, viewModel: viewModel)
}
func bind(view:View, viewModel:ViewModel) {
// View -> ViewModel 的绑定, View把事件传给ViewModel
view.numClickBlock = {
self.viewModel.addAction()
}
// ViewModel -> View 的绑定,ViewModel把展示数据传给View更新
viewModel.updatedBlock = { evt, numStr in
view.numButton.setTitle(numStr, for: .normal)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
viewModel.subAction() // 模拟业务逻辑中的数据更新
}
}
复制代码
观察上述代码,你会发现 View 和 Model 的复用非常 easy,不需要任何改动,it‘s cool.