MVC和MVVM的详细分析

1、MVC

说起MVC,必须拿斯坦福大学公开课上的这幅图来说明,这可以说是最经典和最规范的MVC标准

MVC.gif

所以看懂这张图,你就应该明白MVC在iOS中的实现思路了。

一句话描述就是Controller负责将Model的数据用View显示出来,换句话说就是在Controller里面把Model的数据赋值给View,比如在controller中写self.label.text = self.data[@”title”],只是还没有刻意建一个Model类出来而已。

1.1、MVC是如何产生的

我们所有的App都是界面和数据的交互

  • 需要类来进行界面的绘制,于是出现了View
  • 需要类来管理数据于是出现了Model
  • 我们设计的View应该能显示任意的内容比如UILabel显示的文字应该是任意的而不只是某个特定Model的内容,所以我们不应该在View的实现中去写和Model相关的任何代码,如果这样做了,那么View的可扩展性就相当低了。
  • 而Model只是负责处理数据的,它根本不知道数据到时候会拿去干啥,它既然无法接收用户的交互,它就不应该去管和视图相关的任何信息,所以Model中不应该写任何View相关代码。
  • 然而我们的数据和界面应该同步,也就是一定要有个地方要把Model的数据赋值给View,而Model内部和View的内部都不可能去写这样的代码,所以只能新创造一个类出来了,取名为Controller。

1.2、MVC是如何进行工作的

这张图把MVC分为三个独立的区域,并且中间用了一些线来隔开,C和V以及C和M之间的白线,一部分是虚线一部分是实线,这就表明了引用关系:C可以直接引用V和M,而V和M不能直接引用C,而V和M之间则是双黄线,它们俩谁也不能引用谁,你既不能在M里面写V,也不能在V里面写M。

So
它们三个分别是什么,干什么事,现在我们来看看它们如何进行交互,你可以理解为如何传值。

1.2.1、View和Controller的交互
  • iOS中的传值包括了事件的传递,比如按钮点击事件,是View来接收的,但是处理这个事件的应该是Controller,所以View把这个事件传递给了Controller,如何传递的呢,见图,看到View上面的action没有,这就是事件,看到Controller上面的target没有,这就是靶子,View究竟要把事件传递给谁,它被规定了传递给靶子,Controller实际上就是靶子,View只负责传递事件,不负责关心靶子是谁。这是V和C的一种交互方式,叫做target-action。旁边画出了V对C的另一种传值:协议-委托。委托有两种:代理和数据源。什么是代理,就是专门处理should、will、did事件的委托,什么是数据源,就是专门处理data、count等等的委托。
  • 总结一下,就是主要通过三种方式:action-target用来负责传递特定的事件;dataSource-protocol用来通过回调的形式动态通过数据绘制界面;delegate-protocol提前约定了对一些事件的处理规则,当被规定的事件发生后,就按照协议的规定来进行处理。协议委托可以通过协议方法的参数由V向C传值。比如cell点击事件的协议方法,tableView通过indexPath参数告诉C是哪个cell被点击了。
1.2.2、Model和Controller的交互
  • M就是数据管理者,你可以理解为它直接和数据库打交道。这里的数据库可能是本地的,也可能是服务器上的,M会从数据库获取数据,也可能把数据上传给数据库。M也将提供属性或者接口来供C访问其持有的数据。我们就拿一个简单的需求作为例子,假如我想在一个模块中显示一段文字,这段文字是从网上获取下来的。
  • 那么使用MVC的话,在C中肯定需要一个UILabel(V)作为属性来显示这段文字,而这段文字由谁来获取呢,肯定是由M来获取了。而获取的地方在哪里呢?通常在C的生命周期里面,所以往往是在C的一个生命周期方法比如viewDidLoad里面调用M获取数据的方法来获取数据。现在问题来了,M获取数据的方法是异步的网络请求,网络请求结束后,C才应该用请求下来的数据重新赋值给V,现在的问题是,C如何知道网络请求结束了?
  • 这里我们一定要换一种角度去思考,我们进一步考虑M和V之间的关系:它们应该是一种同步的关系,也就是,不管任何时刻,只要M的值发生改变,V的显示就应该发生改变(显示最新的M的内容)。所以我们可以关注M的值改变,而不用关心M的网络请求是否结束了。实际上C根本不知道M从哪去拿的数据,C的责任是负责把M最新的数据赋值给V。所以C应该关注的事件是:M的值是否发生了变化。
  • 幸运的是在OC中有一种机制恰好就是来解决“一个对象想要关心另一个对象的属性是否发生了变化”的问题,它叫做KVO。(见图)
  • KVO叫做键值观察,它让一个对象作为观察者去观察另一个对象的由某个键值路径所代表的属性,一旦这个属性发生了变化,那么系统就会调用观察者的一个方法叫做observingValueForKeyPath:…。比如C想要在M的data属性发生改变后刷新界面,那么就只需要向M添加观察者C,观察路径为@”data”,这样就相当于对C来讲,一旦M.data发生了变化,那么C的observingValueForKeyPath方法就会被调用,就可以在这个方法的实现中写self.label.text = self.model.data;这样就实现了M和V的同步。

2、MVVM

MVVM:Model、View、ViewModel。
你会下意识地把它和MVC来对比,你会发现,MVVM多了一个ViewModel而少了Controller。
首先说一下多出来的ViewModel。
VM的意义,和Model一样,在于数据。
Model负责对数据进行取和存,然而我们对数据的操作除了取和存以外,还有一个非常重要的操作:解析

2.1、MVVM的诞生

举个例子比如:

网络请求获取下来一个字典(往往使用JSON格式封装的数据都会以字典的形式获取到),这个字典将作为原始数据存放在Model中。而我们的Controller实际上需要字典中某个key对应的一个数组,然后用这个数组来控制一个UITableView的显示。

这个例子表明了我们往往在不知不觉中把数据解析的操作放到了Controller里面。就像我们之前分析MVC是如何合理分配工作的一样,我们需要数据所以有了M,我们需要界面所以有了V,而我们需要找一个地方把M赋值给V来显示,所以有了C,然而我们忽略了一个很重要的操作:数据解析
在MVC出生的年代,手机APP的数据往往都比较简单,没有现在那么复杂,所以那时的数据解析很可能一步就解决了,所以既然有这样一个问题要处理,而面向对象的思想就是用类和对象来解决问题,显然V和M早就被定义死了,它们都不应该处理“解析数据”的问题,理所应当的,“解析数据”这个问题就交给C来完成了。而现在的手机App功能越来越复杂,数据结构也越来越复杂,所以数据解析也就没那么简单了。如果我们继续按照MVC的设计思路,将数据解析的部分放到了Controller里面,那么Controller就将变得·相当臃肿·。还有相当重要的一点:Controller被设计出来并不是处理数据解析的。

Controller能做的事情:
1、self.view用来作为所有视图的容器;
2、管理自己的生命周期;
3、处理Controller之间的跳转;
4、实现Controller容器。

这里面根本没有“数据解析”这一项,所以显然,数据解析也不应该由Controller来完成。那么我们的MVC中,M、V、C都不应该处理数据解析,那么由谁来呢?既然目前没有类能够处理这个问题,那么就创建一个新的类出来解决不就好了?所以我们聪明的开发者们就专门为数据解析创建出了一个新的类:ViewModel。这就是MVVM的诞生。

2.2、如何实现MVVM

现在我们开始着手实现MVVM之前,我先简单提一下之前遗留的一个问题:为什么MVVM这个名字里面,没有Controller的出现(为什么不叫MVCVM,C去哪了)。
你只需要记住两点:

  • 1、Controller的存在感被完全的降低了;
  • 2、VM的出现就是Controller存在感降低的原因。

在MVVM中,Controller不再像MVC那样直接持有Model了。想象Controller是一个Boss,数据是一堆文件(Model),如果现在是MVC,那么数据解析(比如整理文件)需要由Boss亲自完成,然而实际上Boss需要的仅仅是整理好的文件而不是那一堆乱七八糟的整理前的文件。所以Boss招聘了一个秘书,现在Boss就不再需要管理原始数据(整理之前的文件)了,他只需要去找秘书:你帮我把文件整理好后给我。
那么这个秘书就首先去拿到文件(原始数据),然后进行整理(数据解析),接下来把整理的结果给Boss。所以秘书就是VM了,并且Controller(Boss)现在只需要直接持有VM而不需要再持有M了。如果再进一步理解C、VM、M之间的关系:因为Controller只需要数据解析的结果而不关心过程,所以就相当于VM把“如何解析Model”给封装起来了,C甚至根本就不需要知道M的存在就能把工作做好。那么我们MVVM中的持有关系就是:C持有VM,VM持有M。
所以在实现MVVM中一种必要的思想就是:

一旦在实现Controller的过程中遇到任何跟Model(或者数据)相关的问题,就找VM要答案。

你可能感兴趣的:(MVC和MVVM的详细分析)