面试iOS技术iOS Developer

MVC MVP MVVM的理解(基于iOS)

2016-08-20  本文已影响1050人  见哥哥长高了

MVC

MVC对于每个从事iOS开发的人来说都不陌生,MVC架构是构建iOS应用的标准模板, 作为一种软件设计模式,MVC这个概念已经诞生好多年了。对于刚刚从事iOS工作 或者相当一段时间的人来说都了解Model-View-Controller.苹果从最初的发展初期就引入了这一概念,所以我们不会感觉陌生.
模型-视图-控制器(Model-View-Controller,MVC)是Xerox PARC在20世纪80年代为编程语言Smalltalk-80发明的一种软件设计模式,至今已广泛应用于用户交互应用程序中。在iOS开发中MVC的机制被使用的淋漓尽致,充分理解iOS的MVC模式,有助于我们程序的组织合理性。

MVC

****模型对象****
模型对象属于应用程序的数据部分,其封装了应用程序的数据以及操纵和处理数据的业务逻辑和运算. 例如用户在视图层面所做了导致数据变化的操作,就会有中间的控制器来进行传达,最终表现为模型对象的改变. 当模型对想发生变化了之后也会相应的经过控制器来传递,从而达到视图对象的更新.
****视图对象****
视图对象属于应用层的对象。视图对象知道如何将自己绘制出来,并可能对用户的操作作出响应。视图对象的主要目的,就是显示来自应用程序模型对象的数据,并使该数据可被编辑。尽管如此,在 MVC 应用程序中,视图对象通常与模型对象分离,他们之间的通讯要经过控制器的传达。
****控制器对象****
视图层和模型层相互分离, 而在应用程序的一个或多个视图对象和一个或多个模型对象之间,控制器对象充当媒介作用。控制器对象因此是同步管道程序,通过它,视图对象了解模型对象的更改,反之亦然,控制器主要负责数据的传递解耦等工作。控制器对象还可以为应用程序执行设置和协调任务,并管理其他对象的生命周期。

M和V永远不能相互通信,只能通过控制器传递。控制器可以直接与Model对话(读写调用Model),Model通过通知或者KVO机制与控制器间接通信。控制器可以直接与View对话,通过outlet,直接操作View,outlet直接对应到View中的控件,View通过action向控制器报告事件的发生(如用户的点击事件)。控制器是View的直接数据源(数据很可能是控制器从Model中取得并经过加工了)。控制器是View的代理(delegate),以同步View与Controller。

MVC是一个用来组织代码的权威范式,也是构建iOS App的标准模式。Apple甚至是这么说的。在MVC下,所有的对象被归类为一个model,一个view,或一个controller。Model持有数据,View显示与用户交互的界面,而View Controller调解Model和View之间的交互。然而,随着模块的迭代我们越来越发现MVC自身存在着很多不足。因此,MVVM从其他应用而出,在 iOS中从此我们完全将业务逻辑加以区分并使用这套思想。

虽然MVC架构模式在项目中得到了大家的一直认可,也确实解决很多项目开发过程中的难题,但是也是存在很多问题的:
愈发笨重的Controller

在传统的app中模型数据一般都很简单,不涉及到复杂的业务数据逻辑处理,客户端开发受限于它自身运行的的平台终端,这一点注定使移动端不像PC前端那样能够处理大量的复杂的业务场景。然而随着移动平台的各种深入,我们不的不考虑这个问题。传统的Model数据大多来源于网络数据,拿到网络数据后客户端要做的事情就是将数据直接按照顺序画在界面上。随着业务的越来越来的深入,我们依赖的service服务可能在大多时间无法第一时间满足客户端需要的数据需求,移动端愈发的要自行处理一部分逻辑计算操作。这个时间一惯的做法是在控制器中处理,最终导致了控制器成了垃圾箱,越来越不可维护。

控制器Controller是app的“胶水代码”:协调模型和视图之间的所有交互。控制器负责管理他们所拥有的视图的视图层次结构,还要响应视图的loading、appearing、disappearing等等,同时往往也会充满我们不愿暴露的Model的模型逻辑以及不愿暴露给视图的业务逻辑。这引出了第一个关于MVC的问题...

视图view通常是UIKit控件(component,这里根据习惯译为控件)或者编码定义的UIKit控件的集合。进入.xib或者Storyboard会发现一个app、Button、Label都是由这些可视化的和可交互的控件组成。你懂的。View不应该直接引用Model,并且仅仅通过IBAction事件引用controller。业务逻辑很明显不归入view,视图本身没有任何业务。

厚重的View Controller由于大量的代码被放进viewcontroller,导致他们变的相当臃肿。在iOS中有的view controller里绵延成千上万行代码的事并不是前所未见的。这些超重app的突出情况包括:厚重的View Controller很难维护(由于其庞大的规模);包含几十个属性,使他们的状态难以管理;遵循许多协议(protocol),导致协议的响应代码和controller的逻辑代码混淆在一起。

厚重的view controller很难测试,不管是手动测试或是使用单元测试,因为有太多可能的状态。将代码分解成更小的多个模块通常是件好事。
太过于轻量级的Model

太过于轻量级的Model,早期的Model层,其实就是如果数据有几个属性,就定义几个属性,ARC普及以后我们在Model层的实现文件中基本上看不到代码( 无需再手动管理释放变量,Model既没有复杂的业务处理,也没有对象的构造,基本上.m文件中的代码普遍是空的);同时与控制器的代码越来厚重形成强烈的反差,这一度让人不禁对现有的开发设计构思有所怀疑。
遗失的网络逻辑

苹果使用的MVC的定义是这么说的:所有的对象都可以被归类为一个Model,一个view,或是一个控制器。就这些。那么把网络代码放哪里?和一个API通信的代码应该放在哪儿?
你可能试着把它放在Model对象里,但是也会很棘手,因为网络调用应该使用异步,这样如果一个网络请求比持有它的Model生命周期更长,事情将变的复杂。显然也不应该把网络代码放在view里,因此只剩下控制器了。这同样是个坏主意,因为这加剧了厚重控制器的问题。

那么应该放在那里呢?显然MVC的3大组件根本没有适合放这些代码的地方。
较差的可测试性

MVC的另一个大问题是,它不鼓励开发人员编写单元测试。由于控制器混合了视图处理逻辑和业务逻辑,分离这些成分的单元测试成了一个艰巨的任务。大多数人选择忽略这个任务,那就是不做任何测试。

上文提到了控制器可以管理视图的层次结构;控制器有一个“view”属性,并且可以通过IBOutlet访问视图的任何子视图。当有很多outlet时这样做不易于扩展,在某种意义上,最好不要使用子视图控制器(child view controller)来帮助管理子视图。

在这里有多个模糊的标准,似乎没有人能完全达成一致。貌似无论如何,view和对应的controller都紧紧的耦合在一起,总之,还是会把它们当成一个组件来对待。Apple提供的这个组件一度以来在某种程度误导了大多初学者,初学者将所有的视图全部拖到xib中,连接大量的IBoutLet输出口属性,都是一些列问题。

MVVM

我们采用MVC架构模式的避免了model个view层的通讯,但是事实上也不是绝对的 model层和view层有时候可以跨越controller进行直接通讯, 这一部分的数据和视图之间建立了相互的关联,对MVC架构模式的主旨思想是违背的.于是,有的人就开始想办法经这部分相互关联的View-Controller抽离出来,作为新的架构组件来处理,响应的MVVM就出现在我们的视野中了 .....MVVM据说最早在微软的.NET平台中出现过。在MVVM中他的设计思路和MVC很像。它正式规范了视图和控制器紧耦合的性质,并引入新的组件。

在MVVM里,view和view controller正式联系在一起,我们把它们视为一个组件。视图view仍然不能直接引用模型Model,当然controller也不能。相反,他们引用视图模型view Model。view Model是一个放置用户输入验证逻辑,视图显示逻辑,发起网络请求和其他各种各样的代码的极好的地方。有一件事情不应归入view Model,那就是任何视图本身的引用。view Model的概念同时适用于于iOS和OS X



由于展示逻辑(presentation logic)放在了view Model中(比如Model的值映射到一个格式化的字符串),视图控制器本身就会不再臃肿。当你开始使用MVVM的最好方式是,可以先将一小部分逻辑放入视图模型,然后当你逐渐习惯于使用这个范式的时候再迁移更多的逻辑到视图模型中.使用MVVM会轻微的增加代码量,但总体上减少了代码的复杂性. 再来看MVVM的图示,你会注意到我使用了模糊的动词“notify”和“update”,而没有详细说明该怎么做。你可以使用KVO,就像MVC那样,但这很快就会变得难以管理

MVP

上述MVC、MVVM,真实的业务场景中,如果场景的逻辑异常复杂,在反复的迭代中仍会出现各式各样的问题。真对MVVM我个人理解主要是将原来Controller中处理数据逻辑的代码统一归到一个新的class(viewModel)中去,更甚之网络请求等工作全部从Controller移到viewModel。刚一开始总觉的怪怪的。现阶段客户端开发越来越进入一个2.0的阶段,早期的app功能都相对比较简单,无论是从界面还是从业务逻辑上给人的感觉都是简洁实用,这中间包括UI的设计、功能的设计、产品的设计定位等。随着行业的深入,用户的过渡依赖移动端最终导致业各式各样的业务更加依赖客户端,这就导致客户端的开发不得不向PC端靠齐,在版本的反复迭代中业务场景变的愈发不尽人意,仿佛又回到了软件设计的早期。

在传统软件领域,从MVC的诞生主要是为了解决软件界面的行为的分离,在复杂的业务场景内会进一步区分业务逻辑场景的分离,这些手段的最终的目的都是尽最大限度的降低整个场景的藕合度,使其达到分离的目的,模块与模块最终得到独立,将整个场景化整为零,最终使每个模块在一个零上工作,这对于无论是软件的开发还是后续的维护、以及使用普遍遵循这个原则,现有的模式大概产生了相关的类似架构。


web段以及其他业务层负责从接口层获取数据并执行自己的逻辑
service层为外部提供接口
DTO从负责从DB链接并进行数据读写操作
DB层(物理机负责数据存储)

现有客户度一度采用下面的模式:


客户端通过service拿到json 数据,然后通过MVC的结构展示到UI界面上,在iOS中一直流行MVC的开发模式,通过与传统开发模式对比可以发现,其实
service层-客户端交互与服务端service服务满足外部业务场景无非是两个互逆的过程(一个输出层,一个输入层,都是为了更好的满足的下一步的业务需求,一个是将原始数据逻辑话,一个是将获得逻辑数据存档并且展示到用户面前)。service层根据具体的业务场景提供对应的数据服务,service根据不同的业务场景通过DTO层拿到对应
的数据然后组织好数据提供给外界(service 层负责将原始物理数据转换成对应的逻辑数据提供给外界)。

相反,客户端通过网络层拿到对应的网络数据绘制到对应的View上,但是实际的开发过程中,网络数据与真实客户端使用场景也是有一定的差距,MVVM层将对应的
一部分逻辑处理移植到了ViewModel中,这并没有从根本上解决问题,无非是将代码做了一份对应的copy转移,并没有从根本上达到逻辑分层的概念。相反MVP模
式恰好解决了这一难题,MVP模式衍生于传统service架构,针对不同的业务场景图供对应的匹配的抽象service服务,客户端拿到网络数据后未达到指定的目的,
为满足相同抽象逻辑的业务场景,在客户端网络层与Model层之间加一协议层,Model层实现整个协议层,之后在基于MVC的结构下将一概相同层次的
业务场景绘制解释到对应的View上。


M : 逻辑Model层
V : 视图层
P : protocol协议层

Model层类似于MVVM的ViewModel,主要负责存储抽象逻辑数据,另外Model层主还有部分工作实现对应的协议层协议,提供协议对应的各种属性以及服务。Model经过协议层抽象约束,最后Model被抽象成具有统一抽象逻辑的业务场景,最终Model层在讲数据交付整个MVC结构绘制展示的时间,我们可以按照同一套抽象的逻辑标准去执行。

在传统的web层面,为了满足各式各样的业务逻辑场景服务,最红我们实现软件罗杰的层次的分离,诞生了service服务这个概念(service就类似一个标准尺寸的水龙头出口,只要对应的水龙头都按照这样的规则来生产,service就能够满足格式各样的业务场景,极大的解决的传统软件服务业务场景层次的一系列难题);相同的原理在客户端同样可以使用,为了满足客户端MVC结构层里面的稳定,避免各式各样的业务场景迭代插入不同的逻辑,避免最终软件危机的产生,我们采用追加协议层的模式来满足这一目的。

遍观整个软件开发,从早期的软件开发,到后来软件生产管理的危机,软件开发模式一步步的确立,软件行业的每个阶段都是一个里程碑。这世间没有相对完美独到的设计法则,但是亘古不变永远只有一个那就是软件的开发更佳面相生产化、规范化、更加的利于可维护化。一直以来我本人并不特别的注重软件的设计一定、必须按照某种规则来做,毕竟不同的人、不同的业务场景、不同的工程师总有不同的实际境况,站在一个开发工程师的角度来说我并不固执于都按照固定的规则来(比如说你必须按照某个模式来做,必须用MVVM来做;必须用ReactCocoa信号型机制来做...)。相反我个人认为太过于固执只不过某些人的一厢情愿的罢了。相反我觉得因地制宜、应运而生岂不更加快哉,设计不拘于模式,更多时间更是不局限于思考。无论是MVVM、MVP哪一个不是脱胎于MVC,这个世间万变不离其宗,万千功法始终都离不开一部最终的母经。

上一篇下一篇

猜你喜欢

热点阅读