二. MVVM
1. 什么是MVVM
MVVM是指 Model-View-ViewModel的简称,与MVC的一个主要区别是 view 拥有view model。这个与MVC不一样,MVC中应该是controller拥有view。同时,view model 中应该不包含相关view的代码,这样以来,view model就是独立的,可以与任何view进行绑定。
A view model is like an adapter for the model that makes it suitable for presentation. The view model is also where presentation behavior goes.
MVVM是微软提出的,基于MVC的一种设计模式,更准确来说是基于Martin Fowlers Presentation Model (
http://martinfowler.com/eaaDev/PresentationModel.html)。在MVVM中,与MVC一样也是有3个组件,但是与MVC还是有区别的。在MVVM中,我们倾向于把ViewController和View放在一个整体中,把他们当做MVVM的View组件。与MVC最主要的不同之处在于,我们引入了View Model,View Model会通过observer的方式把更新推送到View组件中,一般来说observer是通过RAC来实现的。在MVVM中,View Model封装了用于View绑定的数据/属性和一些验证逻辑以及操作。
在MVVM中,the view 和 view controller正式连接在一起被作为一个整体来对待, View。View仍然不会持有model,他们会持有view model。view model可以做很多事情,比如验证用户数据的逻辑,view的展示逻辑,发出网络请求,以及其他各种操作。但是view model有一件事不能做,不能持有任何对view的引用,总之,view model要做到在iOS和OS X都能直接被使用(换句话说,不能 #import UIKit.h)。
当你看到上图时,你会发现我只是使用了两个比较含糊的动词 “notify” 和 “update”,但是并没有说明如何去实现。尽管可以像MVC中那样使用KVO来实现,但是很快你就会发现这样会使代码变得不可控。因此,在实际中,我们最好使用ReactiveCocoa把MVVM中各个部分组合在一起。
在我自己看来,View Model主要是用来获取data(如网络请求读取数据库等),对data进行操作(如验证转换格式等),以及控制用于View绑定的数据和属性,总之,就是各种逻辑操作都可以放到这里处理,最终要做到的就是把数据变为可展示的格式,用于UI进行填充。注意,不要有与UIView相关的代码。
2. MVVM的优势
1) View models are testable. (脱离UI之后,测试起来会非常简单方便,之前MVC中那些比如展示效果之类的测试也很容易就可以做到,而且UI的更改不会影响测试case的开发)
2) View models can be used like models. (这里主要的意思是,view model的操作更加简单,比如可以进行copy和序列化操作,这个用来做类似恢复UI非常有用途)
3) View models are (mostly) platform-agnostic. (大部分情况下,甚至可以做到平台无关,比如跨iPhone/iPad/Mac)
4) Views and view controllers are simpler. (把逻辑放到view model之后,view/view controller就会变得很简洁清晰)
3. 在MVVM中View Controller的职责
绑定View与View Model,响应View Model,调用View Model相应的方法,负责View和window的转换,布局UI,动画,设备旋转,展示UI
1) view-model 作为view controller的一个属性存在,view controller 了解 view-model和它的public属性,但是view-model对view controller却一无所知。
2) view-model 应该只暴露尽可能少的信息给view controller,view controller也根本不需要关心view-model是如何获取数据的,view-model可以通过网络或者验证或者计算或者之前已经存在的数据,这些对view controller都是透明的,它只关心他需要的必须的那些数据。
3) 不必使用一个view-model来负责屏幕上所有的数据,可以使用child view-models来负责那些很小并且容易封装的区域的数据。这在那种有view重用机制(例如 table view cell)的地方非常有用。而对于那些不是可复用的view,则没有必要使用child view-models,只需要把view-model直接传递给那个view即可,这种方式还有一种额外的好处,就可以让所有的subviews的更新保持同步,由于他们绑定了同一个view-model,只要view-model中对应的属性发生变化,所有的subviews都会被update。注: 这个也是我之前很困惑的地方,在做之前公司的项目时,我就曾经遇到类似的问题,当时的解决方式是自己实现了一套KVO机制,不过问题也很明显,就是耦合很重,同时内存处理上也很麻烦。
5.
https://github.com/ReactiveCocoa/ReactiveViewModel,这个开源项目默认提供了一个RVMViewModel来作为view model的基类,但是我一直很困惑这个类的用途。参考了一下ash的书,他主要是用RVMViewModel中的active属性来表示view model是否处于激活状态,避免在view model init的时候发起网络请求,而是在view model处于active的时候才发起:
但是这种用法,感觉不是很优雅,使用RACCommand去实现的话,可以比这个要优雅很多,可以参考,
http://codeblog.shape.dk/blog/2013/12/05/reactivecocoa-essentials-understanding-and-using-raccommand/,这篇文章里的思想以及我之前的博客中的介绍和github上的demo。网友Sam lau也给我提供了一个他写的小项目作为参考,对学习RAC非常有好处,
https://github.com/samlaudev/DesignerNewsForObjc
参考资料:
- http://www.objc.io/issues/13-architecture/mvvm/ (翻译: http://objccn.io/issue-13-1/)
- http://cocoasamurai.blogspot.fr/2013/03/basic-mvvm-with-reactivecocoa.html
- http://www.sprynthesis.com/2014/12/06/reactivecocoa-mvvm-introduction/ (翻译:http://yulingtianxia.com/blog/2015/05/21/ReactiveCocoa-and-MVVM-an-Introduction/)
- https://github.com/ReactiveCocoa/ReactiveViewModel
- http://rcdp.io/ViewModel.html
- http://rcdp.io/loadble-viewmodel.html
- http://www.raywenderlich.com/74106/mvvm-tutorial-with-reactivecocoa-part-1,http://www.raywenderlich.com/74131/mvvm-tutorial-with-reactivecocoa-part-2
- http://cocoamanifest.net/articles/2013/10/mvc-mvvm-frp-and-building-bridges.html
- http://twocentstudios.com/blog/2014/06/08/on-mvvm-and-architecture-questions/
- http://www.teehanlax.com/blog/model-view-viewmodel-for-ios/
- http://www.objc.io/issues/13-architecture/viper/
- http://www.objc.io/issues/13-architecture/behaviors/
- http://www.teehanlax.com/blog/krush-ios-architecture/