首页 > 解决方案 > Creating a decoupled Router and ViewFactory

问题描述

I am experimenting with routing in an iOS app.

My current approach is hit a road block however, I am creating a ViewControllerFactory and a NavigationControllerRouter.

The challenge I have is that the ViewControllerFactory needs to instruct the router on which route is required next and the NavigationControllerRouter requires the factory to pass a view controller to the navigation controller.

How can these 2 components communicate without creating a strong coupling?

enum Route {
  case login
  case home
}

protocol ViewControllerFactory {
  func create(for route: Route) -> UIViewController
}

class VCFactory: ViewControllerFactory {
  func create(for route: Route) -> UIViewController {
    switch route {
    case .login:
      let viewController = UIViewController()
      viewController.view.backgroundColor = .purple
      return viewController
    case .home:
      let viewController = UIViewController()
      viewController.view.backgroundColor = .red
      return viewController
    }
  }
}

class NavigationControllerRouter {
  private let navigationController: UINavigationController
  private let factory: ViewControllerFactory

  init(_ navigationController: UINavigationController, factory: ViewControllerFactory) {
    self.navigationController = navigationController
    self.factory = factory
  }

  func route(to route: Route) {
    let viewController = factory.create(for: route)
    navigationController.pushViewController(viewController, animated: true)
  }
}

I set this up as follows

@available(iOS 13.0, *)
class SceneDelegate: UIResponder, UIWindowSceneDelegate {

  var window: UIWindow?

  private lazy var navController = UINavigationController()
  private lazy var router: NavigationControllerRouter = {
    let viewControllerFactory = VCFactory()
    return NavigationControllerRouter(navController, factory: viewControllerFactory)
  }()

  func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    guard let scene = (scene as? UIWindowScene) else { return }
    let window = UIWindow(windowScene: scene)
    configure(window)
  }

  func configure(_ window: UIWindow) {
    self.window = window
    self.window?.makeKeyAndVisible()
    self.window?.rootViewController = navController

    router.route(to: .login)
  }
}

I had considered passing a weak reference for the router into the view controller factory, then injecting that into any rendered views, however this then couples the 2, especially if I decide to use the view factory for something like child views, which shouldn't know about the router at all.

标签: iosswiftroutingfactory

解决方案


我认为传递对 your 的引用没有任何问题VCFactory,只要它是一个引用以确保没有保留周期。

这与 MVP 方法中使用的模式非常相似,其中视图具有对演示者的引用,而演示者具有对视图的引用。

您可以通过使用协议来扩展它,以确保您ViewControllerFactory不包含对路由器的未使用引用。

protocol Routable {
  var router: NavigationControllerRouter? { get }
}

...

class VCFactory: ViewControllerFactory, Routable {

  weak var router: NavigationControllerRouter?
....
}

然后,您应该能够在创建时传递参考

  private lazy var router: NavigationControllerRouter = {
    let viewControllerFactory = VCFactory()
    let router = NavigationControllerRouter(navController, factory: viewControllerFactory)

    viewControllerFactory.router = router

    return router
  }()

推荐阅读