转载请注明出处:http://www.olinone.com/
一直想聊聊这个话题,也有朋友跟我留言,让我讲讲MVVM,只可惜一直没整明白,不敢轻易下笔。针对MVVM,网上有很多不错的文章,比如MVVM介绍、被误解的 MVC 和被神化的 MVVM以及Look at MVVM from a different perspective等等
文章前我想先提几个问题
- MVVM到底是什么?它和MVC有什么区别?
- MVVM中VM到底是个什么角色?它和Controller或者Manager有什么区别?
- ViewController在MVVM中扮演怎样角色?Api数据请求放在哪里?数据流向如何?
MVVM简介
关于MVVM,相信大家或多或少都有了解。引用MVVM介绍文中一图
受MVC或MVP架构的影响,对MVVM最初印象以为这是一个以ViewModel为核心,处理View和Model的开发架构。于是乎在原有MVC的基础上,创建了一个所谓的ViewModel对象,然后把ViewController中的代码移到ViewModel中,在ViewModel里面处理View以及Model的所有逻辑。毕竟大家都在说MVVM可以为ViewController瘦身,这ViewController就剩创建ViewModel的代码,嗯,够瘦身,这就是MVVM!
慢慢的,发现有什么地方不对,哪里不对?第一想法就是ViewController的定位,View?不是,Controller?也不是!毕竟它就创建ViewModel,好像与View、Model也没啥关系。抛开ViewController不谈,突然发现这样的ViewModel、Model以及View不就是MVC,一个以ViewModel为中心的MVC!
错在哪里
核心问题就在于对ViewModel角色的定位不清!基于MVVM设计思路,ViewModel存在目的在于抽离ViewController中展示业务逻辑,而不是替代ViewController,其它视图操作业务等还是应该放在ViewController中实现
既然不负责视图操作逻辑,ViewModel中就不应该存在任何View对象,更不应该存在Push/Present等视图跳转逻辑。因此,ViewModel中绝不应该存在任何视图操作相关的代码
1 2 3 4 5 6 |
@interface ViewModel : NSObject // viewmodel中切不可存在view对象,更不该出现push或者present代码 - (instancetype)initWithTableView:(UITableView *)tableView; @end |
ViewModel做啥
很简单,处理视图展示逻辑,ViewModel负责将数据业务层提供的数据转化为界面展示所需的VO。其与View一一对应,没有View就没有ViewModel
比如,数据业务层传递一个含有性别属性sex的DO对象,0表示男, 1表示女。ViewModel的职责就是将其转化为展示层可显示的VO对象
1 |
self.personVO.sex = personDO.sex == 0 ? @"男": @"女"; |
ViewModel和View一起组成DDD(Model-Driven Design)领域驱动架构体系中的Presentation展示层。在iOS中,数据流向可以表示为ViewModel->ViewController->View,ViewController负责连接VO及其对应的View对象
领域驱动设计
领域驱动设计(DDD)对于安卓童鞋可能非常熟悉,有兴趣的童鞋可以参考这篇文章,本文不做过多讲解,借用其描述介绍几个名词
- VO(View Object):视图对象,用于展示层,它的作用是把某个指定页面(或组件)的所有数据封装起来
- DO(Domain Object):领域对象,就是从现实世界中抽象出来的有形或无形的业务实体
- PO(Persistent Object):持久化对象,它跟持久层(通常是关系型数据库)的数据结构形成一一对应的映射关系,如果持久层是关系型数据库,那么,数据表中的每个字段(或若干个)就对应PO的一个(或若干个)属性
- Domain:领域驱动层,是用户与数据库交互的核心中转站,控制用户数据收集,控制请求转向等
MVVM架构中,ViewModel连接视图View和数据业务Model层,而Domain和Data数据持久层共同组成整个Model层。完整结构如图所示
Model并不表示Model
MVVM架构中的M,并不表示Model对象,而是表示整个数据业务层,对应DDD架构中的Domain层以及数据Data层。业务开发中,一般考虑Api或者DB对象,极少考虑Domain层设计,也不会区分DO或者PO对象。笼统定义Model ,将其传递给展示层ViewModel,久而久之,Model对象承载的信息越来越多,更有甚者,在Model中处理业务逻辑,导致项目维护成本增加,代码中出现if..else的概率也会越来越大
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@interface PersonModel : NSObject @property (nonatomic, assign) NSInteger sex; @property (nonatomic, readonly) NSString *sexDescription; @end @implementation PersonModel // model中不应该存在业务逻辑代码 - (NSString *)sexDescription { return self.sex == 0 ? @"男": @"女"; } @end |
当然,Domain层并不是必须的,实际开发中,需要根据具体复杂度和需求来决定。比如只是纯粹的请求展示界面,设计过多的层次结构反而会增加项目的维护成本。同时,Domain层不应该存在任何状态变量!
Data数据层
ViewModel负责展示层逻辑,而Data层则对应数据层逻辑,一般以Manager或者Service身份存在,数据来源主要包括Api、DB或者Cache等。Data数据层操作对象主要为PO持久化对象,对象一旦创建,原则上不可修改
Data数据层具有独立可测试性,其不依赖视图层而存在。切记不可在Data层操作任何视图对象!
1 2 3 4 5 6 7 8 |
@implementation PersonDBAccess // Data层不应该存在任何视图相关代码 - (NSArray *)fetchPersonModels { [SVProgressHUD showWithStatus:@"加载中。。。"]; } @end |
MVVM奇葩说
文章到此,想必各位对MVVM架构已经有了大致了解。对比安卓童鞋对MVP架构的钟爱,iOS童鞋也许更加青睐MVVM,拌上ReactiveCocoa或者RxSwift,这道菜可以做的更加绚烂多彩!当然,正如唐巧在文中所言:ReactiveCocoa 和 MVVM 不应该被神化,我们需要保持的是一个拥抱变化的心,以及理性分析的态度。在新技术的面前,不盲从,也不守旧,一切的决策都应该建立在认真分析的基础上,这样才能应对技术的变化!
写在文后:
为期两天的SwiftCon已经落幕,虽然离我只有两站地铁,只可惜依然没能前往聆听各位大师的技术分享,特别还有我同事兼朋友刘冠杉的个人首秀。虽然,现在网上也出现了各种不好的声音,但是我仍然相信,大多数分享者还是为此付出了不少心血,值得我们为之鼓掌!
个人技术的成长不在一朝一夕,而是长年累月的付出积累沉淀的结果!Swift3.0不久即将发布,而我个人也会逐渐转移☞对Swift语言的学习。与此同时,随着公司ReactNative项目的上马,我也会加强对RN技术的专研和学习中
感谢你的来访,你可以Follow我的个人GitHub,也可以关注我的新浪微博,下期再见!
请问博主,有相关demo吗?
正在开发中,欢迎follow我的个人Github,第一时间获取最新动态!
一直对MVVM不是很了解,网上文章各种乱讲,看了这篇文章终于有了一点了解 ^_^
海礁牛逼
博文值得拜读。受益了!
请问tableviewcell上某个控件的点击事件,页面跳转放在哪里处理?该怎么处理比较好?
我这边结构一般是这样的:TableViewCell->delegateSource->ViewController->ViewModel,所以cell上点击事件可以传给delegateSource,如果没有设计delegateSource,可以直接传给ViewController,由ViewController处理点击事情和跳转,cell作为纯视图,不处理任何业务逻辑,只做界面排版
博主,文中“DDD(Model-Driven Design)”部分,DDD全称应该为Domain-Driven Design
是的~
我也是做编程的。http://www.3c7.net
欢迎欢迎
胡扯就不对了
哈哈
厉害了
欢迎欢迎
额 看了博主的文章 发现完全对不上了…
我专业术语不会很多
MVVM 现在被我弄成了 每个控制器都有一个VM 请求都实际的数据处理是在VM里处理完 vm给控制器一个方法出发请求
vm 处理完请求的数据转换给对应的Model 然后界面展示的时候 控制在VM里取出对应的数据赋值到对应的View
看你说的没问题,是这样的!
严格意义的 View => ViewModel => Model , 不可以逆向. ViewModel 中只负责存储与 UI 状态相关的东西. 发送网络请求, 调用本地的数据缓存, 也是不应该放在 ViewModel 中的. 正确的 网络请求和访问本地数据缓存, 应该在 Model 层中暴露的接口中实现对两者实现调用.
对的。理解正确!
冒昧的说下我考虑在项目中修改使用的流程。ViewModel 中只负责存储与 UI 状态相关的东西。 发送网络请求, 调用本地的数据缓存, 也是不应该放在 ViewModel 中的(复制下),这里我是专门为此提供了一个Service类,这个类会回调狭义上说Model(Model只是作为一个单一的数据结构,多用JsonModel和YYModel来处理)和Error。View只负责UI展示。 程序开始,在Controller里面使用Service类请求数据,回调Model和Error,Error不为nil处理Error 的情况,如果Error为nil,将Model转化为可用的ViewModel对象(会包含一个Model实例变量和其他可以让View展示的东西,比如String1,String2,String3等,Model变量考虑到后续可能还要传参或其他使用),然后将ViewModel的内容展示到View上。参考流程:Service=>Model(Error)=>ViewModel=>Controller=>View。
以上是我自己考虑使用的,请多多指教。关于你最后一句“应该在 Model 层中暴露的接口中实现对两者实现调用”如果可以,请详细说下。
你的理解没问题,可以在service里面发起请求,将回调的DO传给VM,经VM转化为VO,当然,这个service由controller创建还是VM创建持有其实都可以的,各有利弊,放在Controller里面,VM更干净,放在VM里,模块化更一体,方便单元测试!
博主,git是空的仓库啊。。。
还没上传
就这么点东西 你们都看明白了? 好牛逼!