【问题标题】:How to pass ViewController Reference to Router in VIPER design pattern?如何在 VIPER 设计模式中将 ViewController 引用传递给路由器?
【发布时间】:2018-07-27 08:43:35
【问题描述】:

附注: 这不是一个自以为是的问题。在 VIPER 中连接各种模块是一个合理的疑问。这是一个理论问题,所以没有附加代码。我只需要知道在这种特定情况下我们如何连接 View-Presenter-Router 而不会违反VIPER 的基本规则

我是第一次尝试亲身体验 VIPER。这是我对VIPER的基本理解。

视图:应该显示 UI 控件并捕获 IBActions 并调用其演示者的委托方法来处理事件

Presenter:将处理所有与 UI 相关的数据并准备数据以进行渲染并将数据交还给 View。每当需要屏幕转换时,它都会调用其路由器并要求路由器执行转换

P.S: Presenter 中不会有任何 UIComponents。所以演示者中没有import UIKit 声明。

Router:负责执行屏幕转换,通常借助线框来完成(可选但在应用中有这样的类很好)

Interactor:包含所有业务逻辑。Presenter会在需要基于业务逻辑进行处理时调用Interactor。

实体: POJO 类(简单的 Swift 对象或核心数据实体)。

现在问题来了:

如果我的假设是正确的,Presenter 应该是一个没有UIKit 访问权限的普通 Swift 类。

如果为真,假设我按下我的ViewControllerA 上的一个按钮,我需要在其上按下另一个ViewControllerB,显然ViewControllerA 将与PresenterA 对话并告诉它该按钮已被点击,现在@ 987654329@ 应该与RouterA 交谈并告诉它推送ViewControllerB

因为路由器可以访问UIKit,我可以使用storyboard 实例或xib 轻松创建ViewControllerB 的新实例,但为了推送该实例,我需要ViewControllerA 的实例。

但是PresenterA 不能包含对ViewControllerA 的引用,或者可以在函数中作为参数传递给PresenterA,因为UIViewController 属于UIKit,并且Presenter 不应该有UI 语句。

我能想到的可能解决方案:

解决方案 1:

在创建路由器实例时,将相应的ViewController 实例作为其初始化(依赖注入阶段)的一部分传递,这样路由器将始终引用它所属的 ViewController

解决方案 2:

让路由器声明它的协议并在 ViewController 中实现它,并且每当需要引用 ViewController 时使用路由器的委托。但这与 VIPER 的规则相矛盾,即路由器不应该与 View 通信。

我的想法是正确的吗?我的假设是否正确?如果是,解决此问题的正确方法是什么,请建议

【问题讨论】:

  • 由于Router 是负责导航的对象,它很自然会持有对ViewControllerA 的引用,因为它首先呈现了它。同样,Router 将持有对 UINagivationControllerUITabBarController 的引用(如果您的应用程序使用它们)
  • @mag-zbc :我完全同意您的说法,但我的困惑是何时/如何将 viewController 引用传递给路由器?在初始化时?作为依赖注入的一部分?然后它会创建一个循环,因为要创建 ViewController 我需要路由器,并且路由器需要对 ViewController 的引用:(
  • 你根本没有通过它 - ViewControllerA 应该首先由 Router 呈现,所以它应该在 @987654347 中创建 @ 和 Router 应该保留对它的引用。
  • @mag-zbc:对不起,我想我在这里很困惑,假设我有 loginVC,我需要在登录时显示 HomeVC,在这种情况下,LoginVC -> LoginPresenter -> LoginRouter 和登录路由器将创建故事板中的 HomeVC 实例并推送它。在这种情况下,HomeVC 需要创建自己的 HomePresenter 现在问题是如何打破我上面谈到的死锁。请阅读 -> 作为上述声明中的谈话
  • 整个应用程序中应该只有一个Router,这就是VIPER的全部意义——一个负责导航的对象,而不是每个ViewController都有自己的路由器。另外,你说ViewControllers 有自己的Presenters,我认为ViewControllers 应该 Presenters

标签: ios swift viper-architecture


【解决方案1】:

对于 iOS 应用程序的 VIPER 的任何建议或意见都是值得商榷的,因为 VIPER 并不完全适合 iOS 的 UIKit 设计。但是,如果我可以把我的两分钱放在讨论中:

首先,我认为UIViewController 非常适合VIPER 模式中Presenter 的角色,因此ViewControllerA 不需要与它和Router 之间的任何类进行通信——只需与直接Router

其次,应该只有一个Router 对象——因为应用程序只有一个视图/导航堆栈。出于这个原因,为Router 实现Singleton 模式是理想的,所以ViewControllerA(或Presenter,如果你想让ViewControllers 在该模式中扮演View 的角色)不会'不需要保留对Router 的引用。

第三,您不需要将ViewControllerA 的引用传递给您的Router - Router 应该已经有了对它的引用,因为Router 应该首先呈现它。类似的东西:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
{
    // ...

    window?.rootViewController = Router.shared.rootViewController

    // ...
}

class Router
{
    static let shared = Router()

    let rootViewController = ViewControllerA() // or UINavigationController, or UITabBarController etc.
}

Router 应该跟踪导航堆栈并保持对当前呈现的ViewController 的引用

但这就是我的看法。

【讨论】:

  • @mag-zbc : 非常感谢您抽出宝贵时间解释事情 :) 因此 +1 给我一分钟来验证您提到的事实 :)
  • @mag-zbc : 有一个单例线框/路由器是有道理的 :) 感谢 lemme 对此进行更多探索 :) 虽然我不同意让 ViewController 成为 Presenter(在 ViewController 中合并 View 和 Presenter 的操作)因为我们将拥有与 MVVM 中相同的模块结构 :) 整个想法是避免我们在 MVVM 中遇到的胖模块问题,并使关注点分离并使单元测试更深入、更清晰:)
  • 就像我说的,这是有争议的。 ViewController as Presenter 不一定要胖,因为业务逻辑属于Interactor。在某些方面与其他设计模式的相似之处本身并不是一个错误。
  • 同意,只是不想弄乱设计模式,害怕失去设计模式固有的好处
猜你喜欢
  • 2019-09-01
  • 2019-08-04
  • 1970-01-01
  • 2019-04-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多