iOS 应用架构 - VIPER 开发模式

一、概述

VIPER 模式的理念不属于 MV(X) 系类,其理念来自于建筑设计。

建筑领域流行这样一句话,“我们虽然在营造建筑,但建筑也会重新塑造我们”。正如所有开发者最终领悟到的,这句话同样适用于构建软件。

编写代码中至关重要的是,需要使每一部分容易被识别,赋有一个特定而明显的目的,并与其他部分在逻辑关系中完美契合。这就是我们所说的软件架构。好的架构不仅让一个产品成功投入使用,还可以让产品具有可维护性,并让人不断头脑清醒的对它进行维护!

二、什么是 VIPER

测试永远不是构建 iOS 应用的主要部分。当我们着手改善我们的测试实践时,我们发现给 iOS 应用写测试代码非常困难。因此如果想要设法改变测试的现状,我们首先需要一个更好的方式来架构应用,我们称之为 VIPER

VIPER 是一个创建 iOS 应用简明构架的程序。VIPER 是视图 (View),交互器 (Interactor),展示器 (Presenter),实体 (Entity) 以及路由 (Routing) 的首字母缩写。简明架构将一个应用程序的逻辑结构划分为不同的责任层。这使得它更容易隔离依赖项 (如数据库),也更容易测试各层间的边界处的交互:

 
大部分 iOS 应用利用 MVC 构建,使用 MVC 应用程序架构可以引导你将每一个类看做模型,视图或控制器中的一个。但由于大部分应用程序的逻辑不会存在于模型或视图中,所以通常最终总是在控制器里实现。这就导致一个称为重量级视图控制器的问题,在这里,视图控制器做了太多工作。为这些重量级视图控制器瘦身并不是 iOS 开发者寻求提高代码的质量所要面临的唯一挑战,但至少这是一个很好的开端。

重量级试图控制器:ViewModel 之间没有联系,两者有且只有与 Controller 建立交流

VIPER 的不同层提供了明确的程序逻辑以及导航控制代码来应对这个挑战,利用 VIPER,我们的视图控制器可以简洁高效,意义明确地控制视图。你也会发现视图控制器中代码和所有的其他类很容易理解,容易测试,理所当然也更易维护。

三、基于用例的应用设计

应用通常是一些用户用例的集合。用例也被称为验收标准,或行为集,它们用来描述应用的用途。清单可以根据时间,类型以及名字排序,这就是一个用例。用例是应用程序中用来负责业务逻辑的一层,应独立于用户界面的实现,同时要足够小,并且有良好的定义。决定如何将一个复杂的应用分解成较小的用例非常具有挑战性,并且需要长期实践,但这对于缩小你解决的问题时所要面临的范围及完成的每个类的所要涉及的内容来说,是很有帮助的。

利用 VIPER 建立一个应用需要实施一组套件来满足所有的用例,应用逻辑是实现用例的主要组成部分,但却不是唯一。用例也会影响用户界面。另一个重要的方面,是要考虑用例如何与其他应用程序的核心组件相互配合,例如网络和数据持久化。组件就好比用例的插件,VIPER 则用来描述这些组件的作用是什么,如何进行交互。

四、VIPER 的主要部分

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

按职责划分成五层,是 VIPER 设计模式的核心理念,也是从根本上是他区别于 MV(X) 系类设计模式的特征。

这种分隔形式遵循单一责任原则[1]。交互器负责业务分析的部分,展示器代表交互设计师,而视图相当于视觉设计师。

以下则是不同组件的相关图解,并展示了他们之间是如何关联的:

 
更直观一点:

 

五、各部分详解

1.交互器

交互器在应用中代表着一个独立的用例。它具有业务逻辑以操纵模型对象(实体)执行特定的任务。交互器中的工作应当独立与任何用户界面,同样的交互器可以同时运用于 iOS 应用或者 OS X 应用中。

由于交互器是一个 PONSO (Plain Old NSObject,普通的 NSObject),它主要包含了逻辑,因此很容易使用 TDD(测试驱动开发) 进行开发。
不要诧异于你的实体仅仅是数据结构,任何依赖于应用的逻辑都应该放到交互器中。

2.实体

实体是被交互器操作的模型对象,并且它们只被交互器所操作。交互器永远不会传输实体至表现层 (比如说展示器)。

实体也应该是 PONSOs。如果你使用 Core Data,最好是将托管对象保持在你的数据层之后,交互器不应与 NSManageObjects 协同工作。

3.展示器

展示器是一个主要包含了驱动用户界面的逻辑的 PONSO,它总是知道何时呈现用户界面。基于其收集来自用户交互的输入功能,它可以在合适的时候更新用户界面并向交互器发送请求。
展示器还会从交互器接收结果并将结果转换成能够在视图中有效显示的形式。
实体永远不会由交互器传输给展示器,取而代之,那些无行为的简单数据结构会从交互器传输到展示器那里。这就防止了那些“真正的工作”在展示器那里进行,展示器只能负责准备那些在视图里显示的数据。

4.视图

视图一般是被动的,它通常等待展示器下发需要显示的内容,而不会向其索取数据。视图(例如登录界面的登录视图控件)所定义的方法应该允许展示器在高度抽象的层次与之交流。展示器通过内容进行表达,而不关心那些内容所显示的样子。展示器不知道 UILabelUIButton 等的存在,它只知道其中包含的内容以及何时需要显示。内容如何被显示是由视图来进行控制的。

视图是一个抽象的接口 (Interface),在 Objective-C 中使用协议被定义。一个 UIViewController 或者它的一个子类会实现视图协议。

视图和视图控制器同样会操纵用户界面和相关输入。因为通常来说视图控制器是最容易处理这些输入和执行某些操作的地方,所以也就不难理解为什么视图控制器总是这么大了。为了使视图控制器保持苗条,我们需要使它们在用户进行相关操作的时候可以有途径来通知相关部分。视图控制器不应当根据这些行为进行相关决定,但是它应当将发生的事件传递到能够做决定的部分。

视图和展示器之间边界处是一个使用 ReactiveCocoa 的好地方。在这个示例中,视图控制器可以返回一个代表按钮操作的信号。这将允许展示器在不打破职责分离的前提下轻松地对那些信号进行响应。

5.路由

屏幕间的路径会在交互设计师创建的线框 (wireframes) 里进行定义。在 VIPER 中,路由是由两个部分来负责的:展示器和线框。一个线框对象包括 UIWindowUINavigationControllerUIViewController 等部分,它负责创建视图/视图控制器并将其装配到窗口中。

由于展示器包含了响应用户输入的逻辑,因此它就拥有知晓何时导航至另一个屏幕以及具体是哪一个屏幕的能力。而同时,线框知道如何进行导航。在两者结合起来的情况下,展示器可以使用线框来进行实现导航功能,它们两者一起描述了从一个屏幕至另一个屏幕的路由过程。

六、写在最后

了解一种设计模式基本上只要深度理解被拆分出的各个部分的定义、职能和他们之间的联系,就可以初步掌握该种设计模式的理念,剩下的就只剩实践操作了。所以有时候看不明白不要紧,抓住重点反复琢磨就好。


  1. 1.单一责任原则:把同一类逻辑放到同一个模块,不同逻辑不要放到同一个模块。如果是处理图片的就写到图片的模块里,如果是处理字符串的就写到字符串里,而不要因为它们都是解决相近的问题或者因为它们代码不多就写到一个文件里,这样后期会增加很多维护成本。
------本文结束 感谢阅读------

本文地址:http://kaaaaai.cn/articles/033.html
本文基于 知识共享署名-相同方式共享 4.0 国际许可协议发布
转载请注明出处,谢谢!

众筹项目:拯救世界!
0%