MVC简介
MVC是一个基本机制,用于分类。程序中的所有对象都分到三个营地中的某一个。
(1)Model是对象的集合 对象是程序的行为
Model中的内容完全独立于how your UI works
在计算器中,计算是模型,是计算器要做的,应属于计算器对象,不应该在控制器中
(2)Controller控制Model如何显示在屏幕上
Controller中的内容明确的指出你的界面元素是如何工作的
(3)View是Controller的附属类(minions)
View中的内容是相当通用的,通用的界面元素
三者间如何通讯
(1)Controller -> Model
控制器必须了解Model的一切行为,而且必须完全有能力和Model通信,根据控制器的需要,使用Model的开放API。因为控制器的职责就是使用它的附属(即View)来把Model展示给用户,所以它必须拥有该权限。这是个单向箭头。
(2)Controller -> View
从Controller到View是允许通信的。因为控制器负责通过自身对象向视图发送指令,视图是控制器设计用户界面的新方式,因此,控制器可以做任何它想做的事。当我们有一个控制器的属性,该属性指向视图,我们称它为 Outlet,这样,控制器就可以发送指令到对应的视图。
(3)Model -> View OR View -> Model
Never!!!Model是完全独立于UI的,因此,Model不能和视图层的任何对象通讯。同样的,视图对象不能和任何特定的Model对象通讯,他们需要用一个控制器来传到信息。
Never go across that line, Never.
(4)View -> Controller
视图对象例如button可以与控制器通信,但是你必须小心,因为视图对象是通用的(generic),所以它们不能真正的了解控制器,不知道它们所通信的类在什么位置,只是用一种盲目的结构化的方式与控制器通信。举个例子:
(4.1)target action
控制器本身写下一个target,然后它分出一个action指向View并和View通信,就像一个箭头。当你做了一个操作,比如你是一个button,别人点击了你,或者你是个拖动条,别人移动了你,就会向我发送action。通过这种方式,button或slider就可以和控制器通信,但是它并不知道这是一个什么类型的控制器,是纸牌游戏控制器还是空间游戏控制器。它只知道当某个事件在它身上发生时,它发送一个消息给target,这就是视图和控制器之间通信的盲目的,简单的,结构化的方式。
(4.2)delegate
有时,发生在视图上的事件是比较复杂的,Controller需要知道正在发生的事是什么,同步正在发生的事件。
当我正在一个scroll视图中到处滚动,我想让Controller知道我刚刚做了(did)滚动操作,或者我按住屏幕,将要滑动,我想让Controller知道我将要执行(will)滑动操作,或者scrollViewer需要知道是否允许(should)用户在这里执行滚动操作。
scroll视图本身没有足够的逻辑知道这些问题的答案,所以它所做的事情是,代理给其他对象来回答这些问题。它并不知道那个对象的类是什么,他所知道的只是另一个对象可以回答这些问题,will, should, did, this, that, 或者其他事情。
你将在代理协议中看到它们,协议只是一种和其他对象通信的盲目方式。
(4.3)data source
另一个重要的事情是,View不应该拥有它们所展示的数据。换句话说,数据不应该作为View内部的属性。
比如说,你的iPhone中可能有10000首歌曲。假如在你的View中有一些通用的视图列表,你不能传递10000首歌到它的实例变量中,并期望它能容纳10000首歌来展示给你看。
第一,这样做很低效,第二,这10000首歌属于哪一层?在Model层。因为你的音乐数据库是一个Model,它和UI没有任何关系,View仅仅是歌曲,艺术家,专辑和其他信息的一个列表。某个Controller必须查询这个数据库并告诉一个View如何展示这些歌曲,所以这里需要产生通信。视图展示列表分类,并且当你按下屏幕,你轻轻滑动列表,试图查看更多歌曲。这些通信是如何发生的?答案是我们有另一种特殊的代理:数据源。数据源并不做 will, did, should等事情,它回答数量等等的问题,像有多少首歌。Controller在Model中查找,把10000返回给视图。视图为10000个东西开辟内部空间,但视图并不知道这些东西是什么。
当你滑动滚动条,它发送消息给Controller,比如返回150行后的10条数据,然后你再往下滑,现在显示250行后的另10条数据,此时Controller再次与Model通信,要求得到更多的数据。Controller就是用这种盲目的方式提供数据给View。所以View如何从Model得到数据呢?答案就是通过Controller用这种blind structured的方式实现的。所以数据源就是一种用于获取数据的特殊类型的代理。大部分高级类都有代理,包括will,did,should等,其中一些有数据源,这取决于是否要显示大量数据。而一些简单的数据,比如我给纸牌游戏创建了一个叫PlayingCardView的视图,它有花色和点数两点属性,我们不计算花色和点数,我们把它们设置成属性,所以View里会有设置好的数据,但是它不会拥有这些数据。花色和点数的拥有者仍然是Model。View仅仅是为了显示而获取那些数据。所以对于简单的数据,我们可能会把它传递到View里,但是只是给View来显示。对此,Controller的工作是给View解释并格式化这些Model提供的数据。
(5)Model -> Controller
Model可以和Controller通信吗?不可以!Model对于UI一无所知,所以它不可能像Controller一样与一个UI对象进行通信。
但是有时Model中的数据变了,Controller需要知道这个变化,我们怎样进行这种通信呢?
这可以通过一种电台的模型来理解。
Model,也就是电台,会把信息广播给任何感兴趣的人。
在iOS中为了解决这种问题而使用的技术我们叫做Notification和KVO(Key Value Observing)。当Model中的东西发生了变化,它会通过电台广播一下,随后,Controller会接收到来自电台的信息,它会发现数据在变化,然后它就会通过它的绿箭头与Model进行通信,给我那个改变过的数据。
有些人会问,View可以接收广播吗?它们也许可以,但是你最好不要这样做,因为这样会违背MVC的原则。
(6)MVC -> MVC
我们有这些很好的通信方式和所有的这些规则,可以制作一些小的应用。但是如果我们想要制作一个巨大复杂的应用呢?
一个应用在iPhone或iPad上运行,有多个屏幕,在屏幕上有三个或四个不同的区域都发生着事情,我们该怎么做?
我们会结合多个MVC,因为一个MVC可以将另一个MVC当做视图的一部分,所以一个完整的MVC可以被一个稍大些的MVC当做附属(minions)使用。我们可以一层一层地这样堆叠,就可以制作出越来越复杂的应用。
例如一个日历应用,它展示给你一整年的信息。随后你点击了一个月份,于是它展示给你月视图,月视图看起来和年视图就不太一样了。一个月视图只含有日期,也许在日期上会有一些通知你在某天会有任务的圈圈。随后,你点击了一天,然后你获得了一个日视图,日视图会显示小时信息和这天你的所有任务,随后你点击了任务,你会获得任务视图,它会显示任务的细节。这些View,年视图,月视图,日视图,任务视图,每一个都有他们自己的MVC。但是你可以发现,后三个View都被顶层的年视图当做附属使用来展示更多的细节。
你也在带有tabBarController的应用中见过这些吧。在底部有一个tab bar,有三或四个项可以供你选择,就像这张图片一样。
找到'together' 下面的那个紫色的MVC,它从View指向了另外三个MVC,这就是我们创建tab bar的方式,它们就是它的三个tabs,它们每一个都有完全独立的,可以独自行动的小MVC。它们每一个都是一个可重用的View,它们甚至不知道自己在tab bar中,只知道自己应该做这些事情。所以在这方面,它们是模块化的。
我们当然不希望设计成这样
所有人都在互相通信,我们不能分清谁在和什么通信,这会让调试程序变得异常艰难,而且它无法规模化,你不能通过这种方式创建出大程序,因为你无法弄清楚,什么地方会引发整个程序的崩溃。所以我们绝对不会这样做的,这就是 MVC。