iOS开发中的架构模式

架构模式

架构(Architecture)一词大概源于建筑学,也常指建筑物在其尺度上是如何依靠内部的支撑物相互结合而稳固构造的方式。

软件架构是一个系统的草图。软件架构描述的对象是直接构成系统的抽象组件。各个组件之间的连接则明确和相对细致地描述组件之间的通讯,在实现阶段这些抽象组件被细化为实际的组件,比如具体某个类或者是对象。

为什么要注重架构模式

假如你不关心架构,那么总有一天,需要在同一个庞大的类中调试若干复杂的事情,你会发现在这样的条件下,根本不可能在这个类中快速的找到以及有效的修改任何bug。当然,把这样的一个类想象为一个整体是困难的,因此,有可能一些重要的细节总会在这个过程中会被忽略。而且在这样的开发环境中工作你可能会遇到以下类似的问题:

  1. 这个类是一个UIViewController的子类
  2. 数据直接在UIViewController中存储
  3. UIView类几乎不做任何事情
  4. Model 仅仅是一个数据结构
  5. 单元测试覆盖不了任何用例

所以说为了避免发生以上的这些问题我们必须要注重程序的架构,我认为一个好的架构应该具备以下几种特点:
1. 任务均衡分摊给具有清晰角色的实体
2. 可测试性通常都来自与上一条(对于一个合适的架构是非常容易)
3. 易用性和低成本维护

常用的架构方式

  • MVC
  • MVP
  • MVVM
  • VIPER
MVC、MVP、MVVM

在常用的架构模式中前三种可以统称为MV(X)类型,因为MVP、MVVM 都是有MVC 演变而来的。这三种架构方式大致把一个应用中的实体分为以下三类:
- Models–负责主要的数据或者操作数据的数据访问层,可以想象 Perspn 和 PersonDataProvider 类。
- Views–负责展示层(GUI),对于iOS环境可以联想一下以 UI 开头的所有类。
- Controller/Presenter/ViewModel负责协调 Model 和 View,通常根据用户在View上的动作在Model上作出对应的更改,同时将更改的信息返回到View上。

这样分类能给我们工程师带来以下几种便利或好处:
- 更好的理解他们之间的关系
- 复用(尤其是View 和Model)
- 独立单元测试

VIPER

VIPER有以下几部分组成
- 视图(View):根据展示器的要求显示界面,并将用户输入反馈给展示器。
- 交互器(Interactor):包含由用例指定的业务逻辑。
- 展示器(Presenter):包含为显示(从交互器接受的内容)做的准备工作的相关视图逻辑,并对用户输入进行反馈(从交互器获取新数据)
- 实体(Entity):包含交互器要使用的基本模型对象。
- 路由(Routing):包含用来描述屏幕显示和显示顺序的导航逻辑。

MVC

MVC 全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。MVC被独特的发展起来用于映射传统的输入、处理和输出功能在一个逻辑的图形化用户界面的结构中。

传统的MVC

iOS开发中的架构模式

在这里,View并没有任何界限,仅仅是简单的在Controller中呈现出Model的变化。想象一下,就像网页一样,在点击了跳转到某个其他页面的连接之后就会完全的重新加载页面。尽管在iOS平台上实现这这种MVC模式是没有任何难度的,但是它并不会为我们解决架构问题带来任何裨益。因为它本身也是,三个实体间相互都有通信,而且是紧密耦合的。这很显然会大大降低了三者的复用性,而这正是我们不愿意看到的。

MVC 的愿景

iOS开发中的架构模式

由于Controller是一个介于View 和 Model之间的协调器,所以View和Model之间没有任何直接的联系。Controller是一个最小可重用单元,这对我们来说是一个好消息,因为我们总要找一个地方来写逻辑复杂度较高的代码,而这些代码又不适合放在Model中。

理论上来讲,这种模式看起来非常直观,但是这样的模式会让代码变得格外的臃肿。所有有很多开发者将MVC的缩写展开成(Massive View Controller)。所以,为View controller减负也成为iOS开发者面临的一个重要话题。

MVC 的事实

iOS开发中的架构模式

Cocoa的MVC模式驱使人们写出臃肿的视图控制器,因为它们经常被混杂到View的生命周期中,因此很难说View和ViewController是分离的。尽管仍可以将业务逻辑和数据转换到Model,但是大多数情况下当需要为View减负的时候我们却无能为力了,View的最大的任务就是向Controller传递用户动作事件。ViewController最终会承担一切代理和数据源的职责,还负责一些分发和取消网络请求以及一些其他的任务,所以会让代码变得格外臃肿。

综上所述:Cocoa MVC 看起来是一个相当差的架构方案。但是如果你不想在架构选择上投入更多精力,那么Cocoa MVC无疑是最好的方案,而且你会发现一些其他维护成本较高的模式对于你所开发的小的应用是一个致命的打击。但是就开发速度而言,Cocoa MVC 是组好的架构选择方案。

MVC 的三个特性评估
- 任务均摊–View和Model确实是分开的,但是View和Controller却是紧密耦合的

  • 可测试性–由于糟糕的分散性,只能对Model进行测试

  • 易用性–与其他几种模式相比最小的代码量。熟悉的人很多,因而即使对于经验不那么丰富的开发者来讲维护起来也较为容易。

MVP

MVP 的全称为Model-View-Presenter,Model提供数据,View负责显示,Controller/Presenter负责逻辑的处理。MVP与MVC有着一个重大的区别:在MVP中View并不直接使用Model,它们之间的通信是通过Presenter (MVC中的Controller)来进行的,所有的交互都发生在Presenter内部,而在MVC中View会直接从Model中读取数据而不是通过 Controller。

iOS开发中的架构模式

这看起来更像是苹果所提出的MVC方案,这种模式的名字叫做MVP,那么这就是说苹果的MVC实际上就是MVP了吗?不~并不是这样的。如果你仔细回忆一下,View是和Controller紧密耦合的,但是MVP的协调器Presenter并没有对ViewController的生命周期做任何改变,因此View可以很容易的被模拟出来。在Presenter中根本没有和布局有关的代码,但是它却负责更新View的数据和状态。

单就MVP而言,UIViewController的子类实际上就是Views并不是Presenters。这点区别使得这种模式的可测试性得到了极大的提高,付出的代价是开发速度的一些降低,因为必须要做一些手动的数据和事件绑定。

MVP 和 MVC 的关系和区别

MVC 和 MVP 的关系

  • MVP:是MVC模式的变种。
  • 项目开发中,UI是容易变化的,且是多样的,一样的数据会有N种显示方式;业务逻辑也是比较容易变化的。为了使得应用具有较大的弹性,我们期望将UI、逻辑(UI的逻辑和业务逻辑)和数据隔离开来,而MVP是一个很好的选择。
  • Presenter代替了Controller,它比Controller担当更多的任务,也更加复杂。Presenter处理事件,执行相应的逻辑,这些逻辑映射到Model操作Model。那些处理UI如何工作的代码基本上都位于Presenter。
  • MVC中的Model和View使用Observer模式进行沟通;MPV中的Presenter和View则使用Mediator模式进行通信;Presenter操作Model则使用Command模式来进行。基本设计和MVC相同:Model存储数据,View对Model的表现,Presenter协调两者之间的通信。在 MVP 中 View 接收到事件,然后会将它们传递到 Presenter, 如何具体处理这些事件,将由Presenter来完成。
  • 如果要实现的UI比较复杂,而且相关的显示逻辑还跟Model有关系,就可以在View和 Presenter之间放置一个Adapter。由这个 Adapter来访问Model和View,避免两者之间的关联。而同时,因为Adapter实现了View的接口,从而可以保证与Presenter之 间接口的不变。这样就可以保证View和Presenter之间接口的简洁,又不失去UI的灵活性。

MVC 和 MVP 的区别
MVP与MVC最大的一个区别就是:Model与View层之间倒底该不该通信(甚至双向通信)

MVP 的三个特性评估
- 任务均摊–我们将最主要的任务划分到Presenter和Model,而View的功能较少(虽然上述例子中Model的任务也并不多)。

  • 可测试性–非常好,由于一个功能简单的View层,所以测试大多数业务逻辑也变得简单

  • 易用性–在我们上边不切实际的简单的例子中,代码量是MVC模式的2倍,但同时MVP的概念却非常清晰

MVVM

MVVM是Model-View-ViewModel的简写。微软的WPF带来了新的技术体验,如Silverlight、音频、视频、3D、动画……,这导致了软件UI层更加细节化、可定制化。同时,在技术层面,WPF也带来了 诸如Binding、Dependency Property、Routed Events、Command、DataTemplate、ControlTemplate等新特性。MVVM(Model-View-ViewModel)框架的由来便是MVP(Model-View-Presenter)模式与WPF结合的应用方式时发展演变过来的一种新型架构框架。它立足于原有MVP框架并且把WPF的新特性糅合进去,以应对客户日益复杂的需求变化。

MVVM架构是MV(X)系列最新的一员,因此让我们希望它已经考虑到MV(X)系列中之前已经出现的问题。

从理论层面来讲MVVM看起来不错,我们已经非常熟悉View和Model,以及Meditor,在MVVM中它是View Model。

iOS开发中的架构模式

MVVM 和 MVP 的共同点
  • MVVM将ViewController视作View
  • 在View和Model之间没有紧密的联系

此外,它还有像监管版本的MVP那样的绑定功能,但这个绑定不是在View和Model之间而是在View和ViewModel之间。

MVVM 的实现
  • 基于KVO的绑定库如 RZDataBinding 、SwiftBond、FBKVOController
  • 完全的函数响应式编程,比如像ReactiveCocoa、RxSwift或者 PromiseKit

事实上,听到MVVM就会想到ReactiveCoca,反之亦然。尽管通过简单的绑定来使用MVVM是可实现的,但是ReactiveCocoa却能更好的发挥MVVM的特点。

MVVM 的三个特性评估
- 任务均摊 – 在例子中并不是很清晰,但是事实上,MVVM的View要比MVP中的View承担的责任多。因为前者通过ViewModel的设置绑定来更新状态,而后者只监听Presenter的事件但并不会对自己有什么更新。

  • 可测试性 – ViewModel不知道关于View的任何事情,这允许我们可以轻易的测试ViewModel。同时View也可以被测试,但是由于属于UIKit的范畴,对他们的测试通常会被忽略。

  • 易用性 – 在我们例子中的代码量和MVP的差不多,但是在实际开发中,我们必须把View中的事件指向Presenter并且手动的来更新View,如果使用绑定的话,MVVM代码量将会小的多。

MVVM优点

  1. 低耦合。View可以独立于Model变化和修改,一个ViewModel可以绑定到不同的”View”上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
  2. 可重用性。你可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑。
  3. 独立开发。开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计,生成xml代码。
  4. 可测试。界面素来是比较难于测试的,而现在测试可以针对ViewModel来写。

“MVVM很诱人,因为它集合了上述方法的优点,并且由于在View层的绑定,它并不需要其他附加的代码来更新View,尽管这样,可测试性依然很强。”

MVC,MVP,MVVM三者演化

iOS开发中的架构模式

VIPER

VIPER(View-Interactor-Presenter-Entity-Router)是一个和前几种架构模式完全不同的架构模式。示意图如下:

iOS开发中的架构模式

它相对于之前的MV(X)架构多出来了两个部分:Interactor(交互器)和Router(路由)。

VIPER 和 MV(X)的区别

VIPER把MVC中的Controller进一步拆分成了Presenter、Router和Interactor。和MVP中负责业务逻辑的Presenter不同,VIPER的Presenter的主要工作是在View和Interactor之间传递事件,并管理一些View的展示逻辑,主要的业务逻辑实现代码都放在了Interactor里。Interactor的设计里提出了”用例”的概念,也就是把每一个会出现的业务流程封装好,这样可测试性会大大提高。而Router则进一步解决了不同模块之间的耦合。所以,VIPER和上面几个MVX相比,多总结出了几个需要维护的东西:

  • View事件管理
  • 数据事件管理
  • 事件和业务的转化
  • 总结每个业务用例
  • 模块内分层隔离
  • 模块间通信

在这里面,还可以进一步细分一些职责。VIPER实际上已经把Controller的概念淡化了,这拆分出来的几个部分,都有很明确的单一职责,有些部分之间是完全隔绝的,在开发时就应该清晰地区分它们各自的职责,而不是将它们视为一个Controller。

优点

VIPER的特色就是职责明确,粒度细,隔离关系明确,这样能带来很多优点:

  • 可测试性好。UI测试和业务逻辑测试可以各自单独进行。
  • 易于迭代。各部分遵循单一职责,可以很明确地知道新的代码应该放在哪里。
  • 隔离程度高,耦合程度低。一个模块的代码不容易影响到另一个模块。
  • 易于团队合作。各部分分工明确,团队合作时易于统一代码风格,可以快速接手别人的代码。
缺点
  • 一个模块内的类数量增大,代码量增大,在层与层之间需要花更多时间设计接口。
  • 模块的初始化较为复杂,打开一个新的界面需要生成View、Presenter、Interactor,并且设置互相之间的依赖关系。而iOS中缺少这种设置复杂初始化的原生方式。
特别鸣谢:

http://www.cocoachina.com/ios/20160108/14916.html
https://blog.csdn.net/hudan2714/article/details/50990359
https://www.jianshu.com/p/8418fba98e1a