iOS VIPER架构实践(二):VIPER详解与实现

第一篇文章对VIPER进行了简单的介绍,这篇文章将从VIPER的源头开始,比较现有的几种VIPER实现,对VIPER进行进一步的职责剖析,并对各种细节实现问题进行挖掘和探讨。最后给出两个完整的VIPER实现,并且提供快速生成VIPER代码的模板。

Demo和轮子的github地址是:ZIKViper,路由工具:ZIKRouter。有用请点个star~

两个实现展示了以下问题的解决方案:

  • 如何彻底地解决不同模块之间的耦合
  • 如何在一个模块里引入子模块
  • 子模块和父模块之间如何通信
  • 如何对模块进行依赖注入
  • 面向接口的路由工具

起源

VIPER架构,最初是2013年在MutualMobile的技术博客上,由Jeff Gilbert 和 Conrad Stoll 提出的。他们的博客网站有过一次迁移,原文地址已经失效,这是迁移后的博文:MEET VIPER: MUTUAL MOBILE’S APPLICATION OF CLEAN ARCHITECTURE FOR IOS APPS

这是文章中提出的架构示意图:

 viper-mutualmobile

Wireframe可以看作是Router的另一种表达。可以看到,VIPER之间的关系已经很明确了。之后,作者在2014年在objc.io上发表了另一篇更详细的介绍文章:Architecting iOS Apps with VIPER

在作者的第一篇文章里,阐述了VIPER是在接触到了Uncle Bob的Clean Architecture后,对Clean Architecture的一次实践。因此,VIPER真正的源头应该是Clean Architecture。

Clean Architecture

由Uncle Bob在2011年提出的Clean Architecture,是一个平台无关的抽象架构。想要详细学习的,可以阅读作者的原文:Clean Architecture,翻译:干净的架构The Clean Architecture

它通过梳理软件中不同层之间的依赖关系,提出了一个自外向内,单向依赖的架构,如下图所示:

 CleanArchitecture

越靠近内层,越变得抽象,越接近设计的核心。越靠近外层,越和具体的平台和实现技术相关。内层的部分完全不知道外层的存在和实现方式,代码只能从外层向内层引用,目的是为了实现层与层之间的隔离。将不同抽象程度的层进行隔离,做到了把业务规则和具体实现分离开。你可以把外层看作是内层的delegate,外层只能通过内层提供的delegate接口来使用内层。

Enterprise Business Rules

代表了这个软件项目的业务规则。由数据实体体现,是一些可以在不同的程序应用之间共享的数据结构。

Application Business Rules

代表了本应用所使用的一些业务规则。封装和实现了用到的业务功能,会将各种实体的数据结构转为在用例中传递的实体类,但是和具体的数据库技术或者UI无关。

Interface Adapters

接口适配层。将用例的规则和具体的实现技术进行抽象地对接,将用例中用到的实体类转为供数据库存储的格式或者供View展示的格式。类似于MVVM中把Model的数据传递给ViewModel供View显示。

右下角表示了接口适配层中不同模块间的通信方式。不同的模块在业务用例中产生关联和数据传递。Input、Output就是Use Case提供给外层的数据流动接口。

Frameworks & Drivers

库和驱动层,代表了选用的各种具体的实现技术,例如持久层使用SQLite还是Core Data,网络层使用NSURLSession、NSURLConnection还是AFNetworking等。

总结

可以看到,Clean Architecture里已经出现了Use Case、Interactor、Presenter等概念,它为VIPER的工程实现提供了设计思想,VIPER将它的设计转化成了具体的实现。VIPER里的各部分正是存在着由外向内的依赖,从外向内表现为:View -> Presenter -> Interactor -> EntityWireframe严格来说也是一类特殊的Use Case,用于不同模块之间通信,连接了不同的Presenter

必须要记住的是,VIPER架构是根据由外向内的依赖关系来设计的。这句话是指导我们进行进一步设计和优化的关键。

现有的各种VIPER实现

MutualMobile的那两篇文章虽然已经明确了VIPER各部分之间的职责,并且给出了简单的Demo,但是对Wireframe部分的实现有些争议,解耦做得不够彻底,并且对各层之间如何交互还处在最简单的实现上。之后出现了挺多文章来将VIPER进一步细化,不过某些细节的实现上有些差别,在给出我自己的VIPER之前,我将先对这些实现进行一次综合的比较分析,看看他们都使用了哪些技术,遇到了哪些争议点。不同实现之间已经公认的地方我就不再单独列出了。

Brigade团队的实现

原文地址:Brigade’s Experience Using an MVC Alternative: VIPER architecture for iOS applications

文章把VIPER的优点总结了一下,提出了这样的架构图:

 Brigade’s VIPER

他们对VIPER的各部分都没有异议,只是对Interactor的实现进行了进一步细化。用一个Data Manager提供给各个Use Case管理Entity,比如获取、存储功能。在Service中调用网络层去获取服务端的数据。

文章中还认为应该由Wireframe负责初始化整个VIPER,生成各部分的类,并设置依赖关系,并且引用另一个模块的Wireframe,负责跳转到另一个界面。

和这个实现类似的还有:

针对VIPER需要编写太多初始化代码的麻烦,可以使用Xcode自带的Template解决。而很多作者都提到了一个代码生成工具:Generamba

争议

文章并没有对VIPER进行修改,只是进一步细化了。这应该是一个最简单的实现。如果你要实施VIPER,参照这篇文章来没有什么大问题。但是它没有探讨的问题是:

  • 如何解决不同Wrieframe之间的耦合?
  • Wrieframe如何知道其他模块需要的初始化参数?
  • 在模块间通信时,Interactor的数据如何传递给另一个模块?
  • 父模块和子模块之间是怎样的关系?

Rambler&Co团队的实现

一个对VIPER十分感兴趣的俄国团队,编写了一本关于VIPER的书:The-Book-of-VIPER。并且给出了一个目前网络上实现完成度最高的开源Demo:rambler-it-ios,以及他们用于实施VIPER的库:ViperMcFlurry

他们整理的VIPER架构图如下:

 Rambler&Co's VIPER

和其他实现不同的是,他们把VIPER的初始化和装配工作单独放到了一个Assembly里,Router只做界面跳转的工作。并且把VIPER内不同部分之间的通信统一用Input和Output来表示。Input表示外部主动调用模块提供的接口,Output表示模块通过外部实现所要求的接口,将事件传递到外部。

之所以将模块初始化单独放到Assembly里,是因为Router如果负责初始化本模块,会违背单一职责原则。

争议
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值