1、Presenter可以从视图中查询信息
为了让您满意地回答这个问题,我们需要有关特定案例的更多详细信息。为什么视图不能在回调时直接提供更多上下文信息?
我建议您向 Presenter 传递一个Command对象,这样 Presenter 就不必知道在这种情况下要做什么。Presenter 可以执行对象的方法,在需要时自行传递一些信息,而无需了解视图的任何状态(因此引入了高度耦合)。
- View 处于您称为x的状态(与y和z相对)。无论如何,它都知道自己的状态。
- 用户完成操作。View 通知它的委托人(Presenter)关于完成。因为它是如此复杂,所以它构造了一个数据传输对象来保存所有常用信息。此 DTO 的属性之一是
id<FollowUpCommand> followUpCommand
. View 创建一个XFollowUpCommand
(与YFollowUpCommand
and相对ZFollowUpCommand
)并相应地设置其参数,然后将其放入 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.导航层次
另外,假设第二个模块的视图被推入导航控制器,应该如何处理“后退”操作?
如果你选择“all VIPER”,是的,你必须从视图到它的线框并路由到另一个线框。
要将数据从呈现模块(“第二”)传递回呈现模块(“第一”),请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];
}
我首先认为您需要保留一堆类似于导航堆栈的线框,并且所有“活动”模块的线框都相互链接。但事实并非如此。线框管理模块的内容,但导航堆栈是唯一代表哪个视图控制器可见的堆栈。
4. 消息流
不同的模块应该只通过线框对话还是通过演示者之间的委托进行对话?
如果直接向另一个模块 B 的对象发送来自 Presenter A 的消息,那会发生什么?
例如,由于接收者的视图不可见,因此无法启动动画。Presenter 仍然需要等待 Wireframe/Router。所以它必须将动画排入队列,直到它再次激活。这使得 Presenter 更有状态,这使得它更难使用。
在架构方面,请考虑模块所扮演的角色。在端口/适配器架构中,清洁架构从中挖掘了一些概念,问题更加明显。打个比方:一台计算机有很多端口。USB 端口无法与 LAN 端口通信。每个信息流都必须通过核心路由。
您的应用程序的核心是什么?
你有领域模型吗?您是否有一组从各种模块查询的服务?VIPER 模块以视图为中心。共享的东西模块,就像数据访问机制一样,不属于特定的模块。这就是你所说的核心。在那里,您应该执行数据更改。如果另一个模块变得可见,它会拉入更改的数据。
但是,仅出于动画目的,让路由器知道该做什么并根据模块更改向 Presenter 发出命令。
在 VIPER Todo 示例代码中:
- “列表”是根视图。
- “添加”视图显示在列表视图的顶部。
- ListPresenter 实现了 AddModuleDelegate。如果“添加”模块完成,ListPresenter 会知道,而不是它的线框,因为视图已经在导航堆栈中。
5.保持状态
谁应该保留当前选择的引脚、MapViewController、MapPresenter 或 MapWireframe 的状态,以便我知道,当返回时,哪个引脚应该改变颜色?
没有任何。避免视图模块服务中的状态,以降低维护代码的成本。相反,请尝试弄清楚您是否可以在更改期间传递引脚更改的表示。
尝试获取实体以获取状态(通过 Presenter 和 Interactor 等等)。
这并不意味着您Pin
在视图层中创建一个对象,将其从视图控制器传递到视图控制器,更改其属性,然后将其发送回以反映更改。NSDictionary
带有序列化更改的会做吗?您可以将新颜色放在那里,然后将其从PinEditViewController
背面发送给它的 Presenter,后者会在MapViewController
.
现在我被骗了:MapViewController
需要有状态。它需要知道所有引脚。然后我建议你传递一个更改字典,以便MapViewController
知道该怎么做。
但是您如何识别受影响的引脚?
每个引脚都可能有自己的 ID。也许这个 ID 只是它在地图上的位置。也许它是它在 pin 数组中的索引。无论如何,您都需要某种标识符。或者,您创建一个可识别的包装对象,该对象在操作期间保持引脚本身。(不过,对于改变颜色的目的来说,这听起来太荒谬了。)
发送事件来改变状态
VIPER 非常基于服务。有许多大多是无状态的对象捆绑在一起来传递消息和转换数据。在 Brigade Engineering 的帖子中,也展示了一种以数据为中心的方法。
实体位于相当薄的层中。与我心目中的频谱相反的是域模型。并非每个应用都需要这种模式。不过,以类似的方式对应用程序的核心进行建模可能有助于回答您的一些问题。
与实体作为每个人都可以通过“数据管理器”访问的数据容器相反,域保护其实体。域也会主动通知更改。(通过NSNotificationCenter
, 对于初学者来说。通过类似命令的直接消息调用就更少了。)
现在这可能也适合您的 Pin 盒:
- PinEditViewController 更改引脚颜色。这是 UI 组件中的更改。
- UI 组件更改对应于底层模型的更改。您通过 VIPER 模块堆栈执行更改。(你坚持颜色吗?如果不是,
Pin
实体总是短暂的,但它仍然是一个实体,因为它的身份很重要,而不仅仅是它的价值。)
- 相应的
Pin
已经改变颜色并通过 发布通知NSNotificationCenter
。
- 偶然(也就是说,
Pin
不知道),一些交互器订阅了这些通知并改变了它的视图的外观。
尽管这也可能适用于您的情况,但我认为将编辑绑定