用 VIPER 构建 iOS 应用架构(1)(用心良苦)

网友投稿 639 2022-09-13

本站部分文章、图片属于网络上可搜索到的公开信息,均用于学习和交流用途,不能代表睿象云的观点、立场或意见。我们接受网民的监督,如发现任何违法内容或侵犯了您的权益,请第一时间联系小编邮箱jiasou666@gmail.com 处理。

用 VIPER 构建 iOS 应用架构(1)(用心良苦)

在编程的过程中,让代码具备可读性是非常重要的,除此之外代码还要具备明确的目的、在逻辑方面能和其他代码协调一致。这就是我们常说的软件架构。好的架构不能保证产品成功,但它却会使产品便于维护,不至于让读到的人抓狂。

什么是VIPER?

大多数 iOS 应用正在使用 MVC(模型—视图—控制器)架构。使用 MVC 作为一个程序的体系结构,可以引导你思考各个类是一个模型、视图或控制器。由于大部分应用程序逻辑不属于模型或视图,而是通常在控制器中结束。这便导致了所谓的大规模视图控制器,其视图控制器最终变得繁复巨大。为这些庞大的视图控制器减负是 iOS 开发者必须面对的问题,也是提高代码质量的巨大挑战。

通过定位程序逻辑和导航相关的代码,VIPER 的不同层可以帮助解决这个难题。随着 VIPER 的应用,「待办事项」列表实例中,你会发现视图控制器变得纤小、匀称。视图控制器的代码和所有类都容易理解和测试,更有利于后期维护。

基于用例的应用设计

应用程序常常实现为一组用例。用例被称为验收标准或行为,它同时描述了程序目的。一个列表可能需要按日期、类型或名称进行排序,这就是一个用例。用例是程序的逻辑责任层,应独立于用户接口实现,它们应该是小巧而明确的。决定如何将一个复杂程序拆分成较小的用例是具有挑战性的,同时需要积累实践经验。但它能有效地限制各个问题和类的范围。

用 VIPER 构建应用程序,需要实现一系列组件来满足每个用例。应用逻辑是实现用例的重要且非唯一的组成部分。用例也会影响用户界面。此外,需要考虑用例怎样结合核心部件,比如网络和数据持久性。在用例中,组建就像插件,VIPER 用来描述组件功能,以及它们之间彼此交互的方式。

待办事项应用程序的一个用例或要求就是基于用户选择,对待办事项进行分组。通过将数据转换成用例的逻辑进行分离,我们能够保持用户接口代码的整洁性,方便在测试中包装用例,来确保它以正常方式继续工作。

VIPER 的主要组件

VIPER 的主要组件有以下部分:

视图:显示展示器的要求,并返回用户输入。交互器:包含用例指定的业务逻辑。展示器:包含视图逻辑用于准备显示内容(从交互器接收的)并反馈用户输入(通过显示器请求最新数据)。实体:包含交互器所用的基本模型对象。路由:包含导航逻辑来描述屏幕出现的顺序。

下面是不同组件的示意图以及它们的相互联系:

虽然 VIPER 的组件在应用中可以以任意顺序组合实现,这里我们选择以推荐的实现顺序来介绍组件。你会发现,这个顺序与应用的构建过程基本一致,首先讨论应用产品需要做什么,其次是用户如何与它进行交互。

交互器

交互代表应用程序中的一个用例,它包含业务逻辑用来操纵模型对象(实体)以进行特定任务。交互器所做的工作应该是独立于任何用户界面的。同样的交互器可以在 iOS 应用或 OS X 应用中使用。

因为交互器是一个 PONSO(普通老式 NSObject),它主要包含逻辑,很容易使用 TDD 来开发。

示例程序的主要用例是显示用户接下来的待办事项(即截止于下周末之前的任务)。这个用例的业务逻辑是,寻找今天到下周末之间的待办事项,分配到相对的截止日期:今天、明天、本周后几天或下周。

下面是 VTDListInteractor 的类似方法:

- (void)findUpcomingItems{ __weak typeof(self) welf = self; NSDate* today = [self.clock today]; NSDate* endOfNextWeek = [[NSCalendar currentCalendar] dateForEndOfFollowingWeekWithDate:today]; [self.dataManager todoItemsBetweenStartDate:today endDate:endOfNextWeek completionBlock:^(NSArray* todoItems) { [welf.output foundUpcomingItems:[welf upcomingItemsFromToDoItems:todoItems]]; }];}

实体

实体是由交互器操纵的模型对象(仅由交互器操控),交互器不会将实体传递到表现层(即展示器)。

实体往往也是 PONSOs。如果你正在使用核心数据,你会希望你的管理对象最好保持在数据层后端。交互器不能直接使用 NSManagedObjects。

这是示例应用的实体:

@interface VTDTodoItem : NSObject@property (nonatomic, strong) NSDate* dueDate;@property (nonatomic, copy) NSString* name;+ (instancetype)todoItemWithDueDate:(NSDate*)dueDate name:(NSString*)name;@end

如果你的实体都只是数据结构也别太惊讶,任何依赖于程序的逻辑,都应该在交互器中。

展示器

展示器是一个 PONSO,主要由逻辑组成来驱动用户界面。它知道何时呈现用户界面,收集用户交互过程的输入,用于实时更新 UI,并像交互器发送响应请求。

- (void)addNewEntry{ [self.listWireframe presentAddInterface];}

展示器还可以显示交互器接收结果,并将结果转换成其它能在视图中有效展示的形式。

下面是从展示器接收到待办事项后所调用的方法。它将处理相关数据,并确定将哪些内容展现给用户:

- (void)foundUpcomingItems:(NSArray*)upcomingItems{ if ([upcomingItems count] == 0) { [self.userInterface showNoContentMessage]; } else { [self updateUserInterfaceWithUpcomingItems:upcomingItems]; }}

实体从来不会从交互器传输到展示器。相反,那些无行为的简单数据结构却可以传输。这样可以防止任何「实际工作」在展示器中进行。展示器只负责准备视图显示中的数据。

视图

视图通常是被动的。它显示展示器传输来的内容;却不能向展示器主动请求数据。为一个视图定义的方法(例如 LoginView 需要登录界面),应该允许展示器在更高的抽象级进行通信,展示器直接展示其内容,而不关心该内容要如何显示。展示器不知道 UILabel、UIButton 等控件,只知道维护内容以及显示时机。内容要如何展示完全取决于视图。

视图是一个抽象的接口,适用协议用 Objective-C 中定义。一个 UIViewController 或它的子类将实现 View 协议。例如本例中的「添加」界面有如下接口:

@protocol VTDAddViewInterface - (void)setEntryName:(NSString *)name;- (void)setEntryDueDate:(NSDate *)date;@end

视图和视图控制器还处理用户交互和用户输入。所以不难理解为什么视图控制器通常很庞大,因为他们最容易处理用户输入并执行相关动作。为了保持视图控制器倾斜,需要让它们在用户采取某些动作后,通知有效途径告知有关各方。视图控制器不对用户动作做出响应,只将事件传递给响应方法。

本例中,添加视图控制器的事件处理属性,有如下接口:

@protocol VTDAddModuleInterface - (void)cancelAddAction;- (void)saveAddActionWithName:(NSString *)name dueDate:(NSDate *)dueDate@end

路由

界面之间的路由在交互设计师创建的线框中定义。在 VIPER 中,路由的任务是实现展示器和线框之间的共享。线框对象包括 theUIWindow、UINavigationController 和 UIViewController 等,它负责创建视图/视图控制器,并在窗口中完成装配。

由于展示器包含响应用户输入的逻辑,所以它知道何时该导航到其他屏幕,应导航到哪个界面,同时,线框知道如何进行导航。展示器主要使用线框实现导航功能。线框和展示器协同描述一个屏幕到下一个的路由的过程。

线框便于处理导航过渡动画。来看看下面添加线框的例子:

@implementation VTDAddWireframe- (void)presentAddInterfaceFromViewController:(UIViewController *)viewController { VTDAddViewController *addViewController = [self addViewController]; addViewController.eventHandler = self.addPresenter; addViewController.modalPresentationStyle = UIModalPresentationCustom; addViewController.transitioningDelegate = self; [viewController presentViewController:addViewController animated:YES completion:nil]; self.presentedViewController = viewController;}#pragma mark - UIViewControllerTransitioningDelegate Methods- (id)animationControllerForDismissedController:(UIViewController *)dismissed { return [[VTDAddDismissalTransition alloc] init];}- (id)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source { return [[VTDAddPresentationTransition alloc] init];}@end

该应用程序使用自定义视图控制器过渡来添加视图控制器。由于线框负责执行过渡,它成为添加视图控制器的过渡委托,并能返回恰当的过渡动画。

用 VIPER 组织应用组件

建造 iOS 应用的架构时需要明白,作为主要开发工具,UIKit 和 Cocaa Touch 的作用是打造应用的「门面」。架构需要与应用的所有组件和平共处,但它也需要为部分框架的使用,以及处于什么位置提供建议。

iOS 应用的主力是 UIViewController,它很时常被认为是取代 MVC 的竞争者,能大量减少使用视图控制器。但是视图控制器是平台的中心:他们处理方向变化、响应用户输入、集成系统组件比如导航控制器。(未完待续...)

敬请持续关注:《用 VIPER 构建 iOS 应用架构》系列(2).

上一篇:小而精的全栈式团队干不过「土八路」?
下一篇:通过Mesos、Docker和Go,使用300行代码创建一个分布式系统
相关文章

 发表评论

暂时没有评论,来抢沙发吧~