关于VIPER的问题 - 清洁架构

时间:2015-03-11 19:28:53

标签: ios architecture software-design viper-architecture

我一直在阅读罗伯特·马丁的Clean Architecture,更具体地说是VIPER

然后我遇到了这篇文章/帖Brigade’s Experience Using an MVC Alternative,其中描述了我目前正在做的事情。

在尝试在新的iOS项目上实施VIPER之后,我遇到了一些问题:

  • 演示者可以查询视图中的信息,还是“信息传递”总是从视图开始? 例如,如果视图在演示者中触发了某个操作,但随后,根据通过该操作传递的参数,演示者可能需要更多信息。 我的意思是:用户点击“doneWithState:”,如果state ==“something”,从视图中获取信息以创建实体,如果state ==“something else”,则在视图中激活某些内容。我应该如何处理这种情况?
  • 让我们说“模块”(VIPER组件组)决定以模态方式呈现另一个模块。谁应该负责决定第二个模块是以模态方式呈现,第一个模块的线框还是第二个模块的线框?
  • 另外,假设第二个模块的视图被推入导航控制器,应该如何处理“后退”动作?我是否应该在第二个模块的视图控制器中手动设置一个“后退”按钮,该操作调用演示者,该调用器调用第二个模块的线框,该线框消除并告诉第一个模块的线框它被解除,以便第一个模块的视图控制器可能想要展示什么?
  • 不同的模块是应该只通过线框还是通过演示者之间的代表进行对话?例如,如果应用程序导航到另一个模块,但之后用户按下“取消”或“保存”并且该选择需要返回并更改第一个模块中的某些内容(可能显示已保存的动画或删除某些内容) )。
  • 假设在地图上选择了一个引脚,而不是显示PinEditViewController。返回时,根据PinEditViewController上的使用操作,可能需要更改所选引脚的颜色。谁应该保留当前所选引脚,MapViewController,MapPresenter或MapWireframe的状态,以便让我知道,当返回时,哪个引脚应该改变颜色?

2 个答案:

答案 0 :(得分:16)

1。可以从视图

中查询Presenter查询信息

为了让您满意,我们需要有关特定案例的更多详细信息。为什么视图不能在回调时直接提供更多上下文信息?

我建议您将Presenter传递给Command对象,以便Presenter不必知道在哪种情况下该做什么。 Presenter可以执行对象的方法,在需要时自己传递一些信息,而不了解视图的状态(从而引入高度耦合)。

  • 视图处于您调用 x 的状态(与 y z 相对)。无论如何,它知道它的状态。
  • 用户完成操作。视图通知其委托人(演示者)有关完成的信息。因为它是如此复杂,它构造一个数据传输对象来保存所有常用信息。 DTO的一个属性是id<FollowUpCommand> followUpCommand。视图会创建XFollowUpCommand(与YFollowUpCommandZFollowUpCommand相对)并相应地设置其参数,然后将其放入DTO。
  • Presenter接收方法调用。无论具体是什么FollowUpCommand,它都会对数据做些什么。然后它执行协议的唯一方法followUpCommand.followUp。具体实施将知道该怎么做。

如果你必须在某些属性上执行switch-case / if-else,大多数情况下它有助于将选项建模为从公共协议继承的对象并传递对象而不是状态。

2。模态模块

呈现模块或呈现的模块是否应该决定它是否为模态? - 所提出的模块(第二个)应该决定,只要它被设计为仅以模态方式使用。将关于事物的知识放在事物本身中。如果它的表示模式取决于上下文,那么模块本身就无法决定。

第二个模块的线框将收到如下消息:

[secondWireframe presentYourStuffIn:self.viewController]

参数是应该进行演示的对象。如果模块设计为以两种方式使用,您也可以传递asModal参数。如果只有一种方法,请将此信息放入受影响的模块(显示的模块)本身。

然后会执行以下操作:

- (void)presentYourStuffIn:(UIViewController)viewController {
    // set up module2ViewController

    [self.presenter configureUserInterfaceForPresentation:module2ViewController];

    // Assuming the modal transition is set up in your Storyboard
    [viewController presentViewController:module2ViewController animated:YES completion:nil];

    self.presentingViewController = viewController;
}

如果你使用Storyboard Segues,你将不得不做一些不同的事情。

3。导航层次结构

  

另外,假设第二个模块的视图被推入导航控制器,应该如何&#34;返回&#34;行动要处理?

如果你去了所有的VIPER&#34;,是的,你必须从视图到其线框并路由到另一个线框。

要将数据从显示的模块(&#34; Second&#34;)传递回演示模块(&#34; First&#34;),请添加SecondDelegate并在FirstPresenter中实施}。在弹出所呈现的模块之前,它会向SecondDelegate发送一条消息,通知结果。

&#34;不要打架架#34;也许你可以通过牺牲VIPER纯粹来利用一些导航控制器的细节。 Segues已经成为路由机制的一个步骤。 Look at VTDAddWireframe用于引入自定义动画的线框中的UIViewControllerTransitioningDelegate方法。也许这有帮助:

- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
    return [[VTDAddDismissalTransition alloc] init];
}


- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented
                                                                  presentingController:(UIViewController *)presenting
                                                                      sourceController:(UIViewController *)source
{
    return [[VTDAddPresentationTransition alloc] init];
}

我首先想到你需要保留一堆类似于导航堆栈的线框,并且所有&#34;活跃&#34;模块的线框彼此链接。但事实并非如此。线框管理模块的内容,但导航堆栈是唯一代表哪个视图控制器可见的堆栈。

4。消息流

  

不同的模块是应该仅通过线框还是通过演示者之间的代表进行对话?

如果您直接向另一个模块B发送来自演示者A的消息,那么会发生什么?

由于接收者的视图不可见,因此动画无法启动。 Presenter仍然需要等待线框/路由器。所以它必须将动画排入队列,直到它再次变为活动状态。这使得Presenter更具有状态,这使得它更难以使用。

架构方面,考虑模块所扮演的角色。在Ports / Adapters架构中,Clean Architecture从中挖掘了一些概念,问题更加明显。作为类比:计算机有许多端口。 USB端口无法与LAN端口通信。每个信息流都必须通过核心路由。

您应用核心的内容是什么?

你有域模型吗?您是否有从各种模块查询的一组服务? VIPER模块围绕视图。与数据访问机制一样,stuff模块共享不属于特定模块。这就是你可以称之为核心的东西。在那里,您应该执行数据更改。如果另一个模块可见,则会提取已更改的数据。

仅仅为了动画目的,让路由器知道该怎么做,并根据模块的变化向Presenter发出命令。

在VIPER Todo示例代码中:

  • &#34; List&#34;是根视图。
  • An&#34; Add&#34;视图显示在列表视图的顶部。
  • ListPresenter实现AddModuleDelegate。如果&#34;添加&#34;模块完成后,ListPresenter将知道,而不是它的线框,因为视图已经在导航堆栈中

5。保持状态

  

谁应该保留当前所选引脚,MapViewController,MapPresenter或MapWireframe的状态,以便让我知道,当返回时,哪个引脚应该改变颜色?

无。避免视图模块服务中的状态,以降低维护代码的成本。相反,尝试弄清楚是否可以在更改期间传递引脚变化的表示。

尝试触及实体获取状态(通过Presenter和Interactor等等)。

这并不意味着您在视图层中创建Pin对象,将其从视图控制器传递到视图控制器,更改其属性,然后将其发回以反映更改。具有序列化更改的NSDictionary会这样做吗?您可以将新颜色放在那里,然后将其从PinEditViewController发送回其Presenter,它会在MapViewController中发布更改。

现在我被骗了:MapViewController需要有州。它需要知道所有引脚。然后我建议你传递一个更改字典,以便MapViewController知道该怎么做。

但是你如何识别受影响的针?

每个引脚可能都有自己的ID。也许这个ID只是它在地图上的位置。也许它是pin数组中的索引。在任何情况下你都需要某种标识符。或者您创建一个可识别的包装器对象,该对象在操作期间保持一个引脚本身。 (但是,为了改变颜色,这听起来太荒谬了。)

将事件发送到更改状态

VIPER非常基于服务。有许多大多数无状态对象绑在一起传递消息并转换数据。在Brigade Engineering的文章中,也展示了以数据为中心的方法。

实体处于相当薄的层次。与我所考虑的频谱相反的是Domain Model。每个应用程序都不需要这种模式。不过,以类似的方式对应用程序的核心进行建模可能有助于回答您的一些问题。

与实体作为数据容器相反,每个人都可以通过&#34;数据管理器&#34;,域保护其实体。域名也会主动通知变更。 (通过NSNotificationCenter,对于初学者来说。通过类似命令的直接消息调用来减少。)

现在这也适用于您的Pin案例:

  • PinEditViewController更改引脚颜色。这是UI组件的更改。
  • UI组件更改对应于基础模型的更改。您可以通过VIPER模块堆栈执行更改。 (你坚持使用颜色吗?如果没有,Pin实体总是昙花一现,但它仍然是一个实体,因为它的身份很重要,而不仅仅是它的价值。)
  • 相应的Pin已更改颜色,并通过NSNotificationCenter发布通知。
  • 通过偶然事件(即Pin不知道),某些Interactor会订阅这些通知并更改其视图的外观。

虽然这也适用于您的情况,但我认为将编辑与

联系起来

答案 1 :(得分:2)

您的大多数问题都在这篇文章中得到解答:https://www.ckl.io/blog/best-practices-viper-architecture(包括示例项目)。我建议你特别注意模块初始化/演示的提示:它由源Router来完成。

关于后退按钮,您可以use delegates将此消息触发到所需模块。这就是我的工作方式,它很有效(即使在插入推送通知后也是如此)。

是的,模块肯定也可以通过using delegates相互通信。对于更复杂的项目来说,这是必须的。