关于MVVM的一点思考

关于MVVM的一点思考

IOS小彩虹2021-07-18 23:04:00150A+A-

可复用的 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.

点击这里复制本文地址 以上内容由权冠洲的博客整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

支持Ctrl+Enter提交

联系我们| 本站介绍| 留言建议 | 交换友链 | 域名展示
本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除

权冠洲的博客 © All Rights Reserved.  Copyright quanguanzhou.top All Rights Reserved
苏公网安备 32030302000848号   苏ICP备20033101号-1

联系我们