移动应用程序开发似乎是一个非常简单的任务。 似乎在那里该怎么办? 我提出了一些意见,并使用了一些体系结构来涂抹它,就是这样,项目准备就绪,您可以将应用程序发送到工作站。 在一系列文章中,我将分享在为大型银行开发应用程序时遇到的功能。
考虑5个重要主题。 当然,大多数问题在社区中已被多次讨论,但每个主题背后都是痛苦,眼泪,浪费的时间,最重要的是,对我们有用的经验,希望对您有用。

在移动应用程序开发的开始,领导者或设计师面临一个问题-使用哪种架构模式? 我们的工作室具有共同的建筑模式MVP。 纯MVP以其纯格式无疑是不错的(请参见下图),但是如果我们不敲定这种模式,我们将不是真正的开发人员。 他们没有停止一个选择,我们从纯MVP中获得了两个分支。

因此,在设计阶段,我们面临着选择一个的任务,并在一种普遍接受且可理解的建筑模式的基础上继续前进。 但是,在这样的早期阶段,我们设法犯了一个错误,随后给我们带来了许多问题。
让我们看一下我们在类固醇上的两个MVP。

该图显示,与通常的MVP相比,变化不大。 在iOS应用程序的屏幕之间切换时,我们注意到了一些问题。 在过渡之前,大量的用于形成新屏幕的逻辑直接集中在UIViewController中,在我们看来,这不太正确,因此我们要做的第一件事是分离Router实体,该实体负责在应用程序中的屏幕之间进行过渡。
SurfMVP中的模型是Presenter调用以检索数据的服务。 通常,一项服务可以解决整个模块的任务,但是在困难的情况下,您必须与多个模块进行交互。
Configurator实体负责构建一个单独的模块,它初始化所有必需的组件,并负责在它们之间创建依赖关系。

SurfMVP的主要特征是MVP中的每个层都由协议分隔。 该图显示了各层的示意图以及它们之间的协议关系。 需要协议,以便每一层彼此分离,并且理论上很容易替换。 每个层都不应该公开实现细节。
让我们分别考虑它们:
ViewInput-实现View本身, Presenter保持链接。 该协议描述了Presenter可以控制View ,传输数据,更改状态等的方法。
ViewOutput-实现Presenter , View拥有一个指向它的链接。 该协议描述了View和生命周期方法中可能发生的一组操作,例如,用户与屏幕交互的事件。
RouterInput-实现Router , Presenter保持指向它的链接,因为它是唯一负责在应用程序中启动进一步导航的人。
ModuleTransitionable-已实现视图 , 路由器会保留一个链接。 这是SurfMVP中唯一的“基本”协议。 为了向路由器提供一组用于应用程序导航的方法,这是必需的。
ModuleInput-实现演示者 。 该协议必须包含方法,通过该方法,持有该协议链接的另一个模块可以更改当前模块的状态。
ModuleOutput-实现调用模块的Presenter,链接保存被调用模块的Presenter 。 如果可以从新闻模块显示配置文件屏幕,则NewsPresenter应该实现ProfileModuleOutput ,并且ProfilePresenter应该包含指向它的链接。
ModuleOutput传递到被调用模块的Configurator并安装在Presenter中 。 包含影响调用模块行为的模块方法。
SurfMVP问题
基于以上所有内容,存在一个主要问题-导航。 尽管突出显示一个单独的路由器实体的消息是导航问题,但事实证明,这些问题已经消失了,但是并没有持续很长时间。 SurfMVP已成功用于具有简单平面导航 ,而没有复杂的DeepLink和Push-Notifications的项目。
下图示意性地显示了具有SurfMVP的应用程序中的导航。 每个单独的模块都通过自己的路由器相互通信。 因此,构建了应用程序中任何流程的导航。

这种导航非常适合用户在应用程序中进行选择时的情况。 例如,在下面的图像中:用户从A点到D点浏览屏幕,因此他自己建立了一个堆栈,需要以相同的方式返回该堆栈。

问题是在需要将用户从A点转移到D点的时候开始的,而这应该在没有他的参与的情况下发生。 例如,如果用户单击“推送通知”或跟随来自第三方应用程序的链接。 对于SurfMVP,我们将必须添加一个全局路由器,该路由器将控制导航,而不管用户当前在应用程序中的位置如何。 为了在全球范围内解决此问题,我们决定使用协调器,让我们继续进行下去。

协调SurfMVP

协调的SurfMVP是一种架构模式,其中不同于SurfMVP,我们删除了每个单独模块内部的Router实体。 构建应用程序的范例已发生了一些变化。 模块不再完全独立。 除了完全可重用的模块外,每个模块都位于单独的单独的UserFlow中,按照计划,该UserFlow应该执行一些常规操作,使用户达到所需的结果。
付款在我们的应用程序中就是一个例子。 付款是一组屏幕,允许用户以不同的方式进行转帐或付款。
在Coordinated SurfMVP中,路由器实体替换了协调器实体,该实体现在不仅负责导航一个单独的模块,而且还负责导航一组在逻辑上相互连接的模块。 这简化了导航和使用应用程序。 从原理上讲,我们的应用程序如下所示:

最顶层是ApplicationCoordinator,它负责应用程序中的初始路由。 例如,在某种情况下,当用户被授权时,我们将立即将其发送到应用程序的主要部分,否则,我们会将其发送到授权屏幕。
如果我们的应用程序中有Deeplinks或Push-Notifications,则始终可以为协调器设置初始化和启动规则,以便它们将堆栈直接构建到所需的D点,这在我们之前已经讨论过。

在示意图上,我们的导航现在看起来像这样。 每个单独的UserFlow都引用其自己的协调器,由协调器决定将来会发生什么。 现在,传输数据和启动进一步导航的责任在于协调员,他已经与其他模块或其他协调员相关联,以继续构建导航堆栈。
SurfMVP的优缺点
优点:
- 协调器方法的主要优点是能够重用应用程序中的整个导航块。 现在,可以从应用程序中的任何位置调用此协调器,而无需考虑完成工作。
- 由于导航逻辑被隔离在单独的协调器中,因此现在进行导航更加方便:只需打开一个文件,然后将整个图片展示在眼前即可。 不再需要刺穿所有单个模块以了解它们的作用范围,组装应用程序并查看设计。
- 在大型团队中进行设计更方便。 在单独的新功能的设计阶段就足以分配时间来构建整个导航并初始化所有模块,然后它将开发委托给大量开发人员,并且这些屏幕之间的集成问题将大大减少。
- 深度链接和推送通知的集成不再令人头疼。
缺点:
与任何架构方法一样,Coordinated SurfMVP也有缺点。
- 大协调员受伤了。 由于所有逻辑都集中在一个地方,因此要淹没大量的代码行变得更加困难。 如果您不遵循分担责任的原则,那么协调器当然会成长为一个庞然大物,并且代码可读性的所有优势都将轻易消失。
- 您必须编写很多代码才能实现美观。 由于应用程序中的层很多,每个层负责一个单独的动作,因此您必须突破这些层才能到达所需的协调器。
- 内存泄漏-问题不是新问题,但是您应该遵循此问题,以免陷入困境。 与协调器一起工作时内存泄漏的主要原因是模块回调中的保留周期。 因此,您需要仔细监视闭包内部的强链接。
典型案例典型的情况是新协调器的初始化和闭包finishFlow的实现。 必须捕获weak coordinator
器,否则协调器将引用自身,这将导致以AuthCoordinator的形式泄漏。
func runAuthFlow() { let coordinator = AuthCoordinator(router: MainRouter()) coordinator.finishFlow = { [weak self, weak coordinator] in self?.removeDependency(coordinator) } self.addDependency(coordinator) coordinator.start() }
结论
在设计阶段,我们低估了项目的复杂性,并选择了错误的架构方法。 但是此错误有助于形成一组规则,并在初始化项目时更仔细地选择体系结构。
何时使用协调的SurfMVP
实际上,当您需要时再使用它,但是我们遵守以下条件:
- 屏幕的结构很复杂,可能会发生变化。
- 有带有导航的Deeplink和/或Push-Notifications;
- 有必要在一个大团队中工作;
何时使用SurfMVP
我们没有忘记我们的第一个建筑模式。 如果满足以下条件,我们在工作室中仍会使用它:
- 该项目足够小,不打算快速发展;
- 该项目的屏幕结构非常简单,并且不受重大更改。
附加材料
在本文中,我与我们在工作时遇到的体系结构共享一个问题。 当然,使用哪种体系结构的选择仍然由您决定。 在下一篇文章中,我将分享大型项目中的后端问题,并介绍如何解决这些问题。 敬请期待!