最近半年在写app的时候,研究了一下各类iOS代码架构,最后选择了VIPER进行实践,在此对实践中遇到的各类设计问题作一番总结,并分享造出的轮子。html
对代码风格和架构有兴趣的同窗,确定都已经在不少地方见过各类架构的介绍。MVC、MVP、MVVM、VIPER,细分程度逐渐上升。这些架构设计都是来自MVC,只是各自用不一样的方式对MVC进行了细分,在此只对MVC、MVP和MVVM做精简介绍,想要详细了解能够参考这些文章:前端
iOS 架构模式–解密 MVC,MVP,MVVM以及VIPER架构,ios
Model-View-Controller
。MVC简单地将一个模块分为3部分:架构
MVC的划分粒度很粗,所以有不少种具体实现,各个实现有差别,所以并无一个十分明确的标准定义。app
苹果的Cocoa Touch就遵守了MVC的设计,一个界面分为UIView和UIViewController,UIView负责渲染和接收触摸事件,UIViewController负责子view之间的布局、组合、更新以及事件处理。框架
尽管苹果已经给咱们提供了简单的MVC支持,可是在实践中咱们却经常没有遵照MVC。缘由在于Cocoa Touch中的Model部分是由咱们本身负责管理的,并无提供原生的设计支持。因此有时候会出现这样的状况:一个UIView为了方便,提供了一个从某个model进行配置的方法。乍一看十分合理,可是仔细想一想就会发现,这么作已经将View和Model耦合,不符合苹果官方的MVC规范(The Role of View Controllers)。dom
另外,UIViewController存在的一些问题,致使了它很容易变得臃肿和耦合。mvvm
首先,UIViewController和UIView耦合得十分紧密,致使UIViewController常常和某些具体的UIView耦合,几乎没法重用。并且在测试的时候,很难作到单独测试没有View的那部分代码,由于在写的时候就很容易将View的逻辑入侵到各处,Controller会受到View的状态的影响,没法稳定测试。所以,应该尽可能把和View无关的代码放到UIViewController以外。函数
第二,UIViewController负责了界面跳转的操做,界面跳转的相关配置是直接在对应的UIViewController实例上设置的,这样就很容易把源界面和目的界面耦合起来,简单地把界面跳转的部分单独抽离为一个封装好的跳转方法能够必定程度上减小这部分耦合,但也不可避免地会多写许多代码。
所以,苹果的MVC,其实是Model-View-ViewController
。它是一个视图驱动的设计,Controller只是为了管理View而存在的。苹果把UIViewController和Model的关系设计交给了咱们本身。因此,如何把一个UIViewController进行更明确的分工,就是这些架构要作的事。
Model-View-Presenter
用一个Presenter,把Controller中View的部分剔除,实现了View和Model的隔绝。各部分分工以下:
在iOS里,UIView和UIViewController共同组合成了MVP中的View。UIView负责元素的展现,UIViewController负责界面布局和组合,并把事件转发给Presenter。 所以在MVP里,业务逻辑被放到了Presenter中,由它负责协调View和Model。而因为View的抽离,Presenter的状态是可控的,在测试时更不容易受外部影响。
在iOS中使用MVP很简单,在View和Presenter之间用protocol作好事件传递就能够。缺点就是多了一层用于隔离的接口,会致使代码数量增大。
可是随着界面愈来愈复杂,Presenter中的业务代码也会愈来愈庞大,总有一天会遇到一个新的问题:如何再细分Presenter。
Model-View-ViewModel
模式,它也和MVP同样,目的是解决View和Model的耦合。各部分分工以下:
在MVP中,View经过接口的方式来描述本身,在MVVM中,则经过ViewModel来描述本身的特征。那么ViewModel如何将本身的变化更新到View上呢?MVVM常常和数据绑定一块儿出现,在UIViewController中,将View和ViewModel的属性用相似KVO的方式进行绑定,这样ViewModel的变化就能当即传输到View上。
利用ReactiveCocoa和RxSwift这些函数式响应编程框架实现数据绑定,能够用不多的代码完成复杂的业务逻辑,熟练时可以提高开发速度。可是数据绑定的缺点也很明显:调试困难,数据来源难以回溯,在线上出bug的时候就很难追踪了,因此从这方面来讲又下降了维护的效率。
其实数据绑定只是一种为了减小胶水代码的技术实现方式,MVVM的设计并无要求必需要使用数据绑定,你也彻底可使用protocol的方式来将ViewModel的变化传递给View,让数据流向更清晰。MVVM的关键是将View进行了抽象,从而实现View和Model的解耦。
可是除了数据绑定,MVVM还有另外一个问题。把业务逻辑放到ViewModel中,虽然可以为UIViewController减负,可是只是把问题转移了,最终ViewModel仍是会变成另外一个Massive ViewModel。
并且当ViewModel维护Model和业务逻辑时,可复用性就会大大下降。例如把同一个登陆界面复用到另外一个app中时,login model中的属性名或者类型极可能会改变,从而数据处理的方式也会改变,致使ViewModel没法重用。而当View由多个子View组成时,ViewModel里也会引入多个子ViewModel,这就又致使了View的实现影响了ViewModel的实现。奇怪的是,国内iOS圈对这个问题的探讨十分稀少。
ViewModel究竟是什么?从它的命名和最初的设计来看,它只是View的抽象,目的是方便和Model进行数据转换。其实在微软的WPF和前端里,MVVM的业务逻辑大部分是放在Model层的,相关的讨论能够参考:
MVVM: ViewModel and Business Logic Connection
Where does business logic sit in MVVM?
而针对这个问题,有人又提出了一个MVVMP架构(Model-View-ViewModel-Presenter
),把业务逻辑放到了Presenter里。Presenter的引入让ViewModel专一于View的抽象,和Model分离开来,只负责管理View相关的状态、传递View的事件,所以ViewModel中的代码能够获得很好的复用。而Presenter负责大部分业务逻辑,若是模块须要重用,则把业务逻辑中的数据操做逻辑(domain logic)单独分离出来做为重用代码,其余的没法重用的应用逻辑(application logic)则依旧放在Presenter里。
和MVP相比,MVVM用了一种更优雅的方式来抽象View。但它和MVP实际上是相似的,只作了View和Model的解耦,仍然没有对Controller进行进一步的细分。
那么如何对Controller进行进一步的职责细分呢?答案就是VIPER。
VIPER的全称是View-Interactor-Presenter-Entity-Router
。示意图以下:
相比以前的MVX架构,VIPER多出了两个东西:Interactor(交互器)和Router(路由)。
各部分职责以下:
VIPER把MVC中的Controller进一步拆分红了Presenter、Router和Interactor。和MVP中负责业务逻辑的Presenter不一样,VIPER的Presenter的主要工做是在View和Interactor之间传递事件,并管理一些View的展现逻辑,主要的业务逻辑实现代码都放在了Interactor里。Interactor的设计里提出了"用例"的概念,也就是把每个会出现的业务流程封装好,这样可测试性会大大提升。而Router则进一步解决了不一样模块之间的耦合。因此,VIPER和上面几个MVX相比,多总结出了几个须要维护的东西:
而这里面,还能够进一步细分一些职责。VIPER实际上已经把Controller的概念淡化了,这拆分出来的几个部分,都有很明确的单一职责,有些部分之间是彻底隔绝的,在开发时就应该清晰地区分它们各自的职责,而不是将它们视为一个Controller。
VIPER的特点就是职责明确,粒度细,隔离关系明确,这样能带来不少优势:
使用代码模板来自动生成文件和模板代码能够减小不少重复劳动,而花费时间设计和编写接口是减小耦合的路上不可避免的,你也可使用数据绑定这样的技术来减小一些传递的层次。
简单来讲,就是Cocoa框架缺乏一个强大的自定义依赖注入工具。这个问题影响不是特别大,能够选用一些第三方工具来实现,也能够在Router的界面跳转方法里,对模块进行初始化,只不过老是不够完美。针对这个问题,我实现了一个基于protocol声明依赖的界面跳转Router,将会在以后的文章中进行详解。
有人可能会以为,一个界面模块真的有必要使用这么复杂的架构吗?这样是否是过分设计?
我反对这种观点。不要被VIPER的组织图吓到,VIPER并不复杂,它是将原来MVC中的Controller中的各类任务进行了清晰的分解,在写代码时,你会很清楚你正在作什么。事实上,它比使用了数据绑定技术的MVVM更加简单,就是由于它职责明确。从MVC转到VIPER的过程一样是很清晰的,它甚至把重构的思路都体现出来了。而MVVM则留下了许多还没有明确的责任,致使不一样的人会在某些地方有不一样的实现。即使你还在使用MVC,你也应该在Controller中分离出VIPER总结出的那些专项职责,既然如此,为什么不完全地明确这些职责,把它们分散到不一样的文件中呢?一旦开始这样的工做,你就已经向VIPER靠拢了。
有人可能会以为,VIPER适合大型app,中小型app不必过早使用。
我反对这种观点。VIPER是单个界面模块内的架构设计,并非整个app架构层面的设计,和app的总体架构没有多大的关系,也不存在过早使用VIPER的状况。因此,严格来讲,是复杂界面更适合VIPER,而不是大型app更适合VIPER。
至此,个人结论就是,快点拥抱VIPER的怀抱吧。。
VIPER是2013年首次在iOS平台上提出的设计,十分年轻,所以缺乏大量参与者,以总结出更多最佳实践。下一篇文章将会从VIPER的源头开始,比较现有的各类VIPER实现,总结出一个我认为较好的实施方案。
地址:iOS VIPER架构实践(二):VIPER详解与实现。里面有VIPER的具体Demo和代码模板。