关于VIPER的问题 - 清洁架构

Rod*_*uiz 37 architecture software-design ios viper-architecture

我一直在阅读罗伯特·马丁的清洁建筑,更具体地说是VIPER.

然后我遇到了这篇文章/ post Brigade的经验使用MVC替代,它描述了我目前正在做的事情.

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

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

cti*_*tze 16

1.可以从视图中查询Presenter查询信息

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

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

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

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

2.模态模块

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

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

[secondWireframe presentYourStuffIn:self.viewController]
Run Code Online (Sandbox Code Playgroud)

参数是应该进行演示的对象.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;
}
Run Code Online (Sandbox Code Playgroud)

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

3.导航层次结构

另外,假设第二个模块的视图被推入导航控制器,应如何处理"后退"操作?

如果你去"所有VIPER",是的,你必须从视图到其线框并路由到另一个线框.

要将数据从呈现的模块("Second")传递回呈现模块("First"),请添加SecondDelegate并实现它FirstPresenter.在弹出所呈现的模块之前,它会发送消息SecondDelegate以通知结果.

但是,"不要反对框架".也许你可以通过牺牲VIPER纯粹来利用一些导航控制器的细节.Segues已经成为路由机制的一个步骤.查看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];
}
Run Code Online (Sandbox Code Playgroud)

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

4.消息流

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

如果您直接向Presenter A发送另一个模块B的对象,那么会发生什么?

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

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

你的应用程序的核心是什么?

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

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

在VIPER Todo示例代码中:

  • "列表"是根视图.
  • 列表视图顶部显示"添加"视图.
  • ListPresenter实现了AddModuleDelegate.如果"添加"模块完成,ListPresenter将知道,而不是其线框,因为视图已经在导航堆栈中.

5.保持状态

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

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

尝试达到实体以获得状态(通过Presenter和Interactor等等).

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

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

但是,您如何识别受影响的引脚?

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

将事件发送到更改状态

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

实体处于相当薄的层.与频谱相反,我想到的是域模型.每个应用程序都不需要此模式.但是,以类似的方式对应用程序的核心进行建模可能有助于回答您的一些问题.

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

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

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

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


iOS*_*com 8

这个答案可能有点无关,但我把它放在这里供参考.Clean Swift网站是一个很好的实施,即鲍勃叔叔的" 清洁架构 ".所有者将其称为VIP(它仍然包含"实体"和路由器/线框).

该站点为您提供XCode模板.所以假设你要创建一个新场景(他称之为VIPER模块,"场景"),你所要做的就是File-> new-> sceneTemplate.

此模板创建一批7个文件,其中包含项目样板代码的所有问题.它还配置它们,使它们开箱即用.该网站提供了一个非常详尽的解释,说明每件事情如何融合在一

将所有锅炉板代码排除在外,找到解决方案,您上面提到的问题会更容易一些.此外,模板允许全面一致.

编辑 - >关于下面的评论,这里有一个解释为什么我支持这种方法 - > http://stringerstheory.net/the-clean-er-architecture-for-ios-apps/

还有这个 - > 好的,坏的,丑陋的关于iOS的VIPER

  • "网站Clean Swift是一个非常好的实施Bob叔叔的VIPER架构" - 我不同意.VIP周期不是鲍勃叔叔所说的.视图不应直接访问业务逻辑层 - 因此,不应使用循环VC-> I-> P-> VC,而应使用VC-> P-> I,并通过委托进行弱后向引用.View控制器应该只与表示层交互... (4认同)
  • 从我的角度来看,视图控制器是视图(与所有UIView子类一起).它知道UIKit,处理接口方向等.我认为它是视图控制器的定义. (2认同)