概述:
就目前我们在程序开发中应用的架构模式来说,MV(X)系列的架构模式相对来说会比较容易上手、能够满足当前对架构的需求,所以说MV(X)系列目前依旧是主流。当然,VIPER、RAC等这些架构也都是很优秀的架构模式,不过上手会比较慢、响应链比较隐晦出现问题比较难发现,因此这里对于其他的架构模式不做赘述,有兴趣的可以自己去了解。
1、为什么要注重架构模式?
如果不关心、不考虑架构的问题,那么总有一天,我们会在一个庞大的类中去做各种各样复杂的事情。你会发现在这样的情况下,根本不可能在这个类中快速找到以及有效的修改任何bug。而且当这样一个类作为整体时,我们总是很难去抓住重点,并且很多细节可能都会被忽略掉。因此,我们在项目中可能会遇到以下类似的问题:
1、UI、数据、业务等所有内容全部写在了ViewController中
2、数据直接在ViewController中存储、处理
3、UIView几乎不做任何事,或者过多地方从外部访问View,使View变化不可控
4、Model仅仅是一个数据结构,甚至没有所谓的Model层
5、各个类之间相互引用、嵌套,结构复杂且混乱极易出现引用冲突,修改一处代码甚至会引发多处潜在问题
6、模块、功能耦合严重,可拓展性差、甚至基本无法拓展、无法重用
7、在功能开发、问题修改中,可能不得不重构以前的代码
8、单元测试覆盖不了任何用例,甚至已经没有了单元测试的概念、存在的意义
上面只是列举了部分可能会出现的问题,这些往往是我们项目开发中的一些痛点。所以说,为了避免这些问题我们必须要注重程序的架构。而一个比较理想的架构模式是怎样的呢?我认为应该具备以下几个特性:
1、每个类有自己的具体职责(单一职责的原则),任务能够均衡的分配给具体的类
2、不同类间从属、引用关系简单明确,最小引用原则
3、功能易于组件化、可移植性强
4、易用性、低成本维护
2、常用的MV(X)系列架构
我们常说的MV(X)系列架构,就是MVC、MVP、MVVM。而其中的MVP、MVVM其实是从MVC演变而来的。它们之所以可以统称为MV(X)系列,也是因为这几种架构大致将程序分为了以下三类:
1、Model:负责主要的网络数据或本地操作数据的数据访问层,
2、View:负责展示层,在iOS中即可认为所以以UI开头的类
3、Controller/Presenter/ViewModel:负责协调Model和View,根据用户在View上的操作在Model中对数据做对应的修改,同时将修改后的数据返回到View上
通过上面这样的架构分层,会给我们的项目带来这些好处:
1、便于我们去理解它们之间的关系,代码划分更清晰
2、View、Model更加容易复用
3、没有了各种平级类之间的相互引用,类更加安全,且可以进行独立的单元测试
而这三种架构的发展历程大致是这样的:
图片1.png
一、MVC
1、架构总揽
MVC(即Model-View-Controller)是最经典的一种设计模式,也是在早起苹果推荐开发者使用的架构模式。让我们来看下面这张经典的架构图
2617851-04f428288418f297.png从这张图中我们可以看出,Model和View间是完全隔离的,相互间不能去通信,这也使得这两个模块的职责更加的纯粹、安全。Controller在中间充当了协调的作用,绑定持有View、Model对象,负责Model和View间的通信。
2、通信方式:
View和Model间进制通信。
View和Controller间的通信:
View接收到用户的响应事件时,需要将其传递给Controller,如图中View上的action就是事件,而Controller上的target就是目标靶子。也可以通过代理的方式实现。
而Controller的话直接调用View暴露的外部方法即可实现对View的通信。
Model和Controller间的通信:
Model内部数据的变化最终是需要表现在View上的,而Model内部做了什么工作Controller、View是不知道的,我们唯一关心的是当Model中的值发生改变时,View的显示需要同步的改变。所以,我们可以联想到OC中有一个机制可以达到我们的目的,那就是KVO。除了KVO外,像是通知、Block等方式也都可以,Model数据处理完成后告知Controller,由Controller去调度处理结果数据。
Model和Controller间通过通知、KVO、Block等方式来进行通信,具体选择那种方式根据具体场景来决定。View和Controller间一般通过Delegate的方式来通信。
3、优缺点
目前来看的话,MVC整个架构模式还是比较简单、容易上手的,而且也基本能够解决上面我们所提出的问题、满足我们的需求。
那么,MVC既简单容易上手,又结构清晰,那直接用它不就好了?那么事实是怎样的呢?
由于Controller需要承担起View和Model间的通信的任务,所以不可避免的会充斥各种代理和数据传递相关的代码,随着我们业务的增多、页面复杂度的提升,Controller最终也会越来越臃肿。MVC中Controller协调View和Model太多的胶水代码,例如3层View,则必须通过三层的传递才能将事件传递到Controller中。
除此之外,在实际编码过程中,我们可能还会遇到一些问题。
比如,我们判断网络状况的代码写在哪里?写在Model中么?实际上,一般的网络请求的发起和接收我们都放到了Controller中,所以为了方便,索性就也写到了Controller中。
至于View应该是独立的模块了吧?但是实际上,View大多数都放到了Controller中,因为方便么,会有多少人单独写个类来封装View呢?
当我们需要校验用户名、密码等内容时,这样的代码会放在哪里?有多少人会单独封装个工具类呢?基本上都是在Controller中封装个方法来写,有的甚至都不会去封装个方法。
大部分情况下我们为了图快、方便,甚至是对不同层级职责的没有足够的理解,这些因素都会引发各种问题,使写代码越来越难。各种代码没有写在它应该写的地方,而不好的习惯必然就会是的Controller变得越来越臃肿。Controller成为了上帝类,什么都干,本来应该是最简单的架构,但是最后变得令人难懂了。
那么照这么说的话MVC用不了了吗?也不是。在实际开发中我们可以建立中间层,去解决上述问题。
比如,我会将Controller自身的View剥离出来单独封装一个类(作为页面所有子View的父类),然后将所有的View的绑定等操作写在封装好的父类中,这样的话Controller就不会充斥大量的UI相关的代码了,也算一定程度上给Controller减压了。但是这种方法并不能从根本上解决Controller臃肿的问题。
综上所述,MVC虽然有瓶颈,但是也是有它的优势的。如果我们的项目结构、或者说某个功能比较简单,追求快速开发,那么MVC无疑是一个比较好的选择。但若我们的项目结构比较庞大、业务复杂、页面复杂度高,那么我们就需要另寻出路了。
二、MVP
1、架构总揽
MVP(即Model-View-Presenter)来自于MVC,其中的Presenter负责逻辑的处理。在MVC中View和Model间的交互是通过Controller来进行桥接、协调的,而在MVP中它们间的通信是通过Presenter来进行的,所有交互都发生在Presenter内部,极大的分担了Controller的压力。
整体的模块、架构划分如下图所示:
在这种架构里,V指的是ViewController和View,此架构将MVC中的ViewController进行了拆分,视图逻辑数据处理进行了分离成为了这里的P(即Presenter),Model的含义没有变化。View持有Presenter,Presenter持有Model,反之,则不能。
2、通信方式:
View和Model不能通信。
View和Presenter间的交互:
用户通过View触发事件,View/ViewController实际上持有Presenter对象、或者说直接操作Presenter类,在Presenter内部会进行业务、UI逻辑的处理,处理完后通过代理的方式去更新View。
Model和Presenter间的交互:
Presenter实际持有Model,它处理完业务逻辑后直接调用Model进行数据处理,Model返回结果后通过通知、代理、KVO等方式将结果返还给Presenter,Presenter根据结果对View进行更新。
在这里,我们需要注意,Presenter不能持有View,否则会产生耦合,架构设计就没有意义了。在Presenter内部没有和布局有关的代码,但是却负责更新View的数据和状态。原本View和Controller是紧密耦合的,但是我们通过MVP的协调器Presenter却并不会对ViewController的生命周期做任何改变。
那我们的View如何添加到Controller上呢?其实还和前面讲的MVC一样,将ViewController的View剥离出来封装成一个类ParentView,将所有的子View进行集成。
如此一来,我们的整个架构中,ViewController、ParentView里的大量的UI逻辑、业务逻辑代码都被均衡到了Presenter中,ViewController的压力会减小很多,而且逻辑分层更加明确。
3、MVP和MVC的关联和区别:
两者的关系:
MVP是MVC的变种
项目开发中,界面UI是容易变化的,同样的数据也可能有多种显示方式,而业务逻辑也是比较容易变化的,为了使应用拥有比较大的弹性,我们需要将UI、逻辑(包括UI逻辑、业务逻辑)和数据分离开,而MVP则是一个比较好的选择
两者的基本设计思路相同,Model用来存储、处理数据,Controller/Presenter来协调View和Model间的通信。View接收到事件,并传递到Controller/Presenter,在内部进行逻辑处理。
两者的区别:
MVP中定义了一个Presenter协调器,将UI逻辑、业务逻辑进行了剥离,架构分层更加清晰,避免了臃肿的Controller。
MVP的优势:
架构分层更加清晰,将UI逻辑和业务逻辑更好的进行了分离,任务更好的均摊。实现了各模块的解藕
可以高效的使用Presenter,可用于多个视图,而不需要改变Presenter的逻辑
MVP的缺点:
相对MVC的话代码量会更多。
会对开发速度有一定的影响,因为必须做一些手动的数据和事件的绑定。
因为将View的一些逻辑放到了Presenter,所以两者的交互会过于频繁。
三、MVVM
1、架构总揽
MVVM(即Model-View-ViewModel)其实也是MVC的衍生物,这种架构下,将View、Controller看做了统一层级的View(相当于是把Controller的一部分逻辑剥离了出来),数据层的处理由Model负责,ViewModel则是充当了一个UI适配器的角色,从Controller中剥离出来的UI相关的逻辑都放在了ViewModel中,减轻了Controller的负担。
整体的架构分层、交互如下图:
View:视图的展示。包含UIView和UIViewController,View层可以持有ViewModel
ViewModel:视图的适配器。暴露属性并且与View元素的显示内容或状态一一对应。ViewModel层可以持有Model,但是不能持有View层。
Model:数据模型或数据逻辑处理类。数据模型很好理解,就是我们从服务器拉下来的数据,经过解析后的模型。而数据逻辑处理类,就是本地数据操作、或远程数据处理等方法形成的数据管理类。Model不可以持有ViewModel层。
附加说明:
数据绑定:MVVM的核心。它的作用是在View和ViewModel之间进行数据的双向绑定,如果没有这一步,则它和MVP差异不是很大。
Controller:C持有VM,VM持有M,V持有VM同时又依赖于C
Controller的职责:
1)、self.view作为所有视图的容器
2)、管理自己的生命周期
3)、处理不同Controller间的跳转
4)、实现自身容器的作用,
2、通信方式:
View和ViewModel:View层会持有ViewModel,它们之间进行了双向数据绑定,View进行用户交互产生事件后,ViewModel会进行对应的业务下发。而当ViewModel中的属性发生变化后,View监听到了会自动更新UI
ViewModel和Model:ViewModel会持有Model,进行操作下发给Model进行数据处理,Model将结果返还给ViewModel。
从上图中我们可以看到它的架构分层是不是和MVP很像?而且业务划分也是类似的,只不过中间协调者的命名不同而已?事实上,两者最大的区别就在于View-ViewModel间的交互,即双向数据绑定。
什么是双向数据绑定呢?
View和ViewModel间建立了连接,当View变化时会自动更新ViewModel;而ViewModel中的动作完成后,会自动去更新View。
举个例子,在View收到了事件消息,View通过改变ViewModel暴露的属性值,借助KVO,ViewModel收到了View发送的消息,进而去调度Model执行数据处理,当数据发生变化后,会发送消息,View收到消息后更新自身UI
3、双向数据绑定
我们说到MVVM架构,通常就会想到RAC,响应式编程,这个框架能够帮助我们更容易的实现双向数据绑定。通过上面的例子,应该对什么是响应式编程有一个大概的印象了。
MVVM中,如何去实现双向数据绑定呢?如何简化代码?关于这一点,有很多优秀的第三方库去帮助我们实现这一步骤:
基于KVO的绑定库:如RZDataBinding、SwiftBond、FBKVOController
完全的函数响应式编程(不限于双向数据绑定):如ReactiveCocoa、RxSwift或PromiseKit
如果不借助三方库,如何自己去实现双向绑定呢?
方案一:KVO
ViewModel->View。在View中实现KVO,监听ViewModel中的属性,当ViewModel中的属性变化时,会通知View进行UI的更新。
View->ViewModel。因为View持有ViewModel,可以直接通过ViewModel暴露的方法进行更新、操作。
以上,我们通过KVO基本上实现了双向绑定数据,但是通过KVO在实际开发中会存在以下问题:
每次都需要写大量的注册、移除、监听的处理等,代码比较负责
如果没有移除监听的话,可能会直接导致Crash,用起来不够方便
方案二(个人理解):Block、Delegate
它们两个不需要像KVO那样步骤复杂,只需要声明、使用,所以可以将其实现的表达方式进行简化,用类似于KVO的表达方式,也可以实现类似双向数据绑定的模式。
在View中初始化绑定ViewModel的Block、Delegate,并保留一个接口来监听ViewModel的通知,在ViewModel中属性值变化的地方进行Block、Delegate的回调通知即可,不需要区分不同业务,通过返回参数值的方式,使View做不同的更新。
虽然我们也可以通过KVO、类KVO的方式实现双向绑定,不过会稍显复杂,如果大面积使用的话,建议要么自己去封装、简化绑定类,要么使用相关的三方框架来帮助我们实现绑定功能。
4、MVVM和MVP的共同点:
都是将ViewController视作View
在View和Model间没有直接的联系
都是通过中间层来实现View和Model的交互
区别:
MVVM核心思想是双向绑定,View的变动,自动反映在ViewModel上,反之亦然
MVVM中的ViewModel和View没有太多的交流
MVVM中View会承担更多,因为需要通过 ViewModel 的设置绑定来更新状态,而MVP中的View只监听 Presenter 的事件但并不会对自己有什么更新。
在实际开发中必须把View 中的事件指向 Presenter 并且手动的来更新 View,如果使用绑定的话,MVVM 代码量将会小的多。
MVVM的优点:
1、低耦合。View可以独立于Model的变化、修改,一个ViewModel可以绑定到不同的View上,当View变化时Model可以不变。
2、独立开发。开发人员可以分别进行View、ViewModel的开发
3、MVVM中的View要比MVP中的View承担更多的任务。因为前者通过VViewModel的设置绑定来更新状态,而后者只监听Presenter的事件但并不会对自己有什么更新。
MVVM的缺点:
学习成本高
上手慢,开发速度慢
Debug困难(调用栈会比较复杂)
对于过大的项目,数据绑定需要花费更多的内存。
附:
关于MVVM架构在编码中,具体如何划分UI层、业务层、数据层?层级间如何通信?如何更加高效的代码实现?如何组件化实现复用?编码中如何防止内存泄漏?等等问题,都需要我们在实际编码中尝试、熟悉后才能慢慢体会出来,严格遵守不同架构中的编码要求,才能更加优雅的去写代码。
更加详细的编码规范、技巧等,参考MVVM中的Demo,诊断信息导出功能模块的代码实现则严格根据MVVM架构进行实现
总结:
1、几种架构的总结、选择:
就目前的应用来看,MVVM无疑是主流架构,无论是美团、腾讯系很多产品都在使用MVVM架构。但是,这并不是说MVC、MVP架构被淘汰了,不能用了。事实上,我们需要针对不同的业务场景去选择不同的架构,没有最好的架构,只有更合适的架构。像是MVVM的话,也并不是说直接照搬这种模式,在很多情况下会根据自己的场景去做改变,根据业务去调整,和MVP、MVC混用等等。
MVC、MVP、MVVM这三种架构在学习成本、开发时间、代码的复杂程度、Debug成本上是递增的,但是在架构逻辑复杂度、总体代码量、可移植的难度是递减的。
如果功能模块相对比较简单、在架构上不想投入太多精力,则选择MVC更加合适。若项目比较庞大,业务逻辑复杂,对代码可移植性(重用)要求比较高,追求更加清晰的架构模式,则MVP、MVVM更加合适。如果我们需要进一步均摊各个架构层级的任务,进一步解藕,简化不同层级间通信代码,并且愿意在架构上投入更多的精力,则MVVM无疑是更优的选择,而MVVM也是目前MV(X)系列中相对来说更加优秀的架构模式。总之,根据自己的实际需求、应用场景选择合适的架构,没有最好的架构,只有更合适的架构。
2、关于解藕、复用:
我们经常在谈复用,但是怎么去理解、并在实际编码过程中实现复用呢?拿View来举例,在MVC中的View不应该引用任何其他自定义类的头文件,而在MVVM中则除了ViewModel外也不应该引入其他自定义类的头文件。只有如此,才能保证View拿到其他环境下最大程度的可以使用,而不需要修改过多的代码。
附言:
上述是从概念、例子去介绍了几种架构,但是并不代表我们一定要严格去那样做,我们需要根据自己的业务对架构进行优化、完善。
具体如何做,还需要大家在写代码过程中进行揣摩、理解。
网友评论