使用VIPER模式进行iOS开发的干净架构

当开始一个iOS项目时,开发人员除了要达到应用程序的目的之外,还将首先关注的障碍之一是,他们需要的Cocoapods将是如何组织代码,以及可能遵循的设计模式。 尽管大多数开发人员会坚持使用久经考验的真正的MVC(模型-视图-控制器)或MVVM(模型-视图-视图模型),但是有一种聪明的模式称为VIPER,许多人都不知道。 VIPER可能会改变您习惯用于iOS平台的开发方式,并且像大多数事物一样,它具有积极和消极的作用。

通常的嫌疑

首次开始iOS开发时,开发人员将听到很多有关MVC或Model-View-Controller的信息。 这种Apple认可的架构模式随处可见,包括Apple的UIKit,大多数教程示例应用程序以及当今App Store上的大多数应用程序。 顾名思义,MVC分为三个职责:

  • 模型:应用程序的数据和操纵该数据的逻辑。
  • 视图:用户操作的用户界面。
  • 控制器:控制模型和视图之间的逻辑。

如果您正在开发第一个应用程序或在一个小型团队中工作,那么MVC可以很好地工作,但是随着应用程序的发展,开发人员开始开玩笑地将MVC称为“大型视图控制器”。随着时间的流逝,越来越多逻辑被推到控制器中,控制器变得becomes肿且不可测试。 这就是VIPER模式试图解决的问题。

什么是VIPER?

VIPER模式是一种遵循单一职责原则的干净架构。 VIPER努力将应用程序的逻辑划分为不同的责任层。 与MVC相比,VIPER更进一步,它分为五个职责:

  • 视图:显示来自演示者的信息,并将用户交互发送回演示者。
  • 交互者:检索实体,并包含特定用例的业务逻辑。 它们与视图无关,可以由一个或多个演示者使用。
  • 演示者:处理为显示准备的内容并拦截用户交互。
  • 实体:简单的数据模型对象。
  • 路由器:处理应显示哪些屏幕以及何时显示的导航逻辑。

实施VIPER时,每个功能或模块都将遵循上述结构。 由于该应用程序的逻辑将分为多个较小的组件,因此视图现在变得更明亮,逻辑也变得更具可测试性。

VIPER的流程

VIPER的基本流程非常简单。 路由器将用户带到新的视图,该视图通知演示者它需要数据,演示者向交互器询问数据,交互器检索实体(从网络请求或本地数据库),交互器将实体发送到演示者,演示者从实体创建视图模型,演示者将视图模型发送到视图,然后视图向用户显示必要的数据。

实施VIPER

为了演示以VIPER模式创建模块,让我们假设我们正在创建一个显示汽车表的应用程序。 每个单元格都会显示汽车的品牌和型号。 用户将能够点击一个单元格并查看汽车的详细信息,或者他们可以单击“创建新汽车”按钮将新汽车添加到列表中。 实施新模块时,我发现自下而上的工作会更容易,因此我们将从定义实体开始。

实体

由于该应用程序正在处理汽车,因此让我们创建一个简单的struct对象,该对象将包含一些基本信息: car对象。

car对象是我们的API服务将返回给我们的东西。 它包含基本信息,例如汽车的ID,品牌,型号和内饰。 但是,当我们要显示有关汽车的信息时,由于表格单元格仅需要显示汽车的制造商和型号,因此不需要包括所有信息。 因此,我们可以创建一个快速视图模型来仅代表汽车的制造商和模型。

快速视图模型将在Presenter中创建,并传递回View。

互动者

现在我们的实体已经建立,让我们为其创建业务逻辑或“用例”。 我们的表格视图将需要使用API​​服务中的汽车填充。 因此,我们将创建一个Interactor,用于处理从API检索汽车并将其发送到Presenter。

为此,我们声明了一个名为getCars的协议方法,该方法将使用我们的API服务获取汽车并将其返回。 由于我们的应用程序非常简单,因此我们不需要其他用例(尽管大多数实际应用程序都有多个用例)。

主持人

有了Interactor,我们现在有了一种方法来检索最终将要显示的汽车。 如前所述,演示者负责对用户的输入做出反应并为显示准备内容。 我们的应用程序描述中提到需要显示汽车(制造商和型号),显示汽车详细信息的能力以及创建新汽车并将其添加到表格中的能力。 接下来,我们将创建一个Presenter,使我们能够做到这一点。

为了展示汽车,我们添加了showCars方法,该方法使用我们先前创建的Interactor检索汽车,然后从这些对象创建简单的视图模型,这些对象将用于视图中特定类型的单元格。 接下来的两个方法showCarDetailshowCreateCarScreen将使用Router(将在下面创建)来将用户导航到正确的屏幕。 我们的视图将使用以下三种方法来启用功能。

路由器

什么是没有导航的应用程序? 下一步将创建一个路由器,该路由器根据操作将用户导航到不同的屏幕。 到目前为止,我们的应用程序要求我们导航到汽车详细信息屏幕并创建新的汽车屏幕。 现在让我们实现这两种方案。

我们的两种方法showCarDetailshowCreateCarScreen只是实例化新的View Controller,并使用呈现的View Controller的Navigation Controller将其推入视图堆栈。

视图

现在,我们有了一种检索汽车以进行显示的方法以及几种将用户导航到不同屏幕的方法,下一步是创建一个依靠Presenter来处理其内容和动作的View Controller。

我们可能注意到的第一件事是我们的View Controller现在非常轻巧。 在我们的View Controller中没有完成任何逻辑,只是简单地调用了Presenter,后者委托应该发生的事情。 没有更多的大规模视图控制器!

使用VIPER进行测试

由于VIPER模式具有不同的职责层,因此可以非常轻松地完成单元测试。 每个级别都可以单独测试,而无需设置复杂的环境。 为了证明这一点,我们可以测试CarPresenter 。 该演示者处理加载汽车数据,显示汽车详细信息屏幕以及显示创建新汽车屏幕。 让我们创建一些测试。

由于我们使用协议创建了Router和Interactor方法,因此我们可以轻松模拟这些类以进行测试。 然后,使用这些测试类创建CarPresenter ,在测试中,我们可以轻松地验证我们的预期行为是否正确。

VIPER的好处

在使用VIPER模式的七个月中,我发现了一些优点:

  • 易于测试:由于VIPER模式的松散耦合性质,与其他模式相比,向每个对象添加单元测试很简单。
  • 处理大型团队:我使用VIPER的项目大约有15个开发人员。 由于模式中模块的分离,因此开发人员可以轻松地在应用程序的不同部分上工作,而不会互相踩踏。 使用VIPER还有助于减少合并冲突。
  • 易于开发:在VIPER上完全设置项目后,向模块添加新功能变得更加容易。 如果您需要一种对事物进行排序的新方法,则可以在Presenter中添加一个新方法。 开发人员可以将VIPER模式与“依赖注入”结合使用,以快速,轻松地添加新功能。

VIPER的负面

尽管VIPER模式可能有助于消除Massive-View-Controllers,但确实需要付出一定的代价。

  • 样板代码太多:如您在上面的示例中看到的,需要创建许多模板代码才能创建一个简单的应用程序。 这会在一开始就减慢开发速度,并增加不必要的复杂性。 文件的增加也会增加编译时间。
  • 高学习曲线:由于VIPER相对于MVC越来越复杂,因此团队可能需要更多讨论和培训,才能在其整个应用程序中正确实施模式。
  • 完全购买:根据我的经验,整个项目都需要使用VIPER模式。 VIPER在Massive-View-Controllers,部分实现的VIPER模块,不使用VIPER的第三方Cocoapods甚至Apple自己的框架中不能很好地发挥作用。 当VIPER与现有代码或新框架一起使用时,需要额外的工作以确保代码兼容性。

结论

尽管iOS开发的世界与MVC紧密相关,但在iOS开发中使用VIPER模式的好处对于正确的项目和团队而言可能是转化性的。 我建议为希望减少冲突,增加测试范围并更明确地区分其代码职责的大型项目和团队评估VIPER模式。