我一直在阅读罗伯特·马丁的Clean Architecture,更具体地说是VIPER。
然后我遇到了这篇文章/帖Brigade’s Experience Using an MVC Alternative,其中描述了我目前正在做的事情。
在尝试在新的iOS项目上实施VIPER之后,我遇到了一些问题:
答案 0 :(得分:16)
为了让您满意,我们需要有关特定案例的更多详细信息。为什么视图不能在回调时直接提供更多上下文信息?
我建议您将Presenter传递给Command对象,以便Presenter不必知道在哪种情况下该做什么。 Presenter可以执行对象的方法,在需要时自己传递一些信息,而不了解视图的状态(从而引入高度耦合)。
id<FollowUpCommand> followUpCommand
。视图会创建XFollowUpCommand
(与YFollowUpCommand
和ZFollowUpCommand
相对)并相应地设置其参数,然后将其放入DTO。FollowUpCommand
,它都会对数据做些什么。然后它执行协议的唯一方法followUpCommand.followUp
。具体实施将知道该怎么做。如果你必须在某些属性上执行switch-case / if-else,大多数情况下它有助于将选项建模为从公共协议继承的对象并传递对象而不是状态。
呈现模块或呈现的模块是否应该决定它是否为模态? - 所提出的模块(第二个)应该决定,只要它被设计为仅以模态方式使用。将关于事物的知识放在事物本身中。如果它的表示模式取决于上下文,那么模块本身就无法决定。
第二个模块的线框将收到如下消息:
[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,你将不得不做一些不同的事情。
另外,假设第二个模块的视图被推入导航控制器,应该如何&#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;模块的线框彼此链接。但事实并非如此。线框管理模块的内容,但导航堆栈是唯一代表哪个视图控制器可见的堆栈。
不同的模块是应该仅通过线框还是通过演示者之间的代表进行对话?
如果您直接向另一个模块B发送来自演示者A的消息,那么会发生什么?
由于接收者的视图不可见,因此动画无法启动。 Presenter仍然需要等待线框/路由器。所以它必须将动画排入队列,直到它再次变为活动状态。这使得Presenter更具有状态,这使得它更难以使用。
架构方面,考虑模块所扮演的角色。在Ports / Adapters架构中,Clean Architecture从中挖掘了一些概念,问题更加明显。作为类比:计算机有许多端口。 USB端口无法与LAN端口通信。每个信息流都必须通过核心路由。
您应用核心的内容是什么?
你有域模型吗?您是否有从各种模块查询的一组服务? VIPER模块围绕视图。与数据访问机制一样,stuff模块共享不属于特定模块。这就是你可以称之为核心的东西。在那里,您应该执行数据更改。如果另一个模块可见,则会提取已更改的数据。
仅仅为了动画目的,让路由器知道该怎么做,并根据模块的变化向Presenter发出命令。
在VIPER Todo示例代码中:
谁应该保留当前所选引脚,MapViewController,MapPresenter或MapWireframe的状态,以便让我知道,当返回时,哪个引脚应该改变颜色?
无。避免视图模块服务中的状态,以降低维护代码的成本。相反,尝试弄清楚是否可以在更改期间传递引脚变化的表示。
尝试触及实体获取状态(通过Presenter和Interactor等等)。
这并不意味着您在视图层中创建Pin
对象,将其从视图控制器传递到视图控制器,更改其属性,然后将其发回以反映更改。具有序列化更改的NSDictionary
会这样做吗?您可以将新颜色放在那里,然后将其从PinEditViewController
发送回其Presenter,它会在MapViewController
中发布更改。
现在我被骗了:MapViewController
需要有州。它需要知道所有引脚。然后我建议你传递一个更改字典,以便MapViewController
知道该怎么做。
但是你如何识别受影响的针?
每个引脚可能都有自己的ID。也许这个ID只是它在地图上的位置。也许它是pin数组中的索引。在任何情况下你都需要某种标识符。或者您创建一个可识别的包装器对象,该对象在操作期间保持一个引脚本身。 (但是,为了改变颜色,这听起来太荒谬了。)
VIPER非常基于服务。有许多大多数无状态对象绑在一起传递消息并转换数据。在Brigade Engineering的文章中,也展示了以数据为中心的方法。
实体处于相当薄的层次。与我所考虑的频谱相反的是Domain Model。每个应用程序都不需要这种模式。不过,以类似的方式对应用程序的核心进行建模可能有助于回答您的一些问题。
与实体作为数据容器相反,每个人都可以通过&#34;数据管理器&#34;,域保护其实体。域名也会主动通知变更。 (通过NSNotificationCenter
,对于初学者来说。通过类似命令的直接消息调用来减少。)
现在这也适用于您的Pin案例:
Pin
实体总是昙花一现,但它仍然是一个实体,因为它的身份很重要,而不仅仅是它的价值。)Pin
已更改颜色,并通过NSNotificationCenter
发布通知。Pin
不知道),某些Interactor会订阅这些通知并更改其视图的外观。虽然这也适用于您的情况,但我认为将编辑与
联系起来答案 1 :(得分:2)
您的大多数问题都在这篇文章中得到解答:https://www.ckl.io/blog/best-practices-viper-architecture(包括示例项目)。我建议你特别注意模块初始化/演示的提示:它由源Router
来完成。
关于后退按钮,您可以use delegates
将此消息触发到所需模块。这就是我的工作方式,它很有效(即使在插入推送通知后也是如此)。
是的,模块肯定也可以通过using delegates
相互通信。对于更复杂的项目来说,这是必须的。