還記得在 2017 的時候,**@khanlou** 曾經給過一個 talk,是關於 Coordinator 的 talk。當時的我也還是個小菜雞,只要看到各種關於 architecture 的東西都會莫名興奮。

這個 talk 想要解決的是當 app 越來越大、越來越複雜的時候的一個處理方式。當時 Coordinator 被提出的時候也並沒有太多人去實作出來,比較偏向是一個概念,以至於小菜雞如果就會開始 google 看看有沒有 library 可以幫我們把 coordinator 實作完,可以讓我們直接套到我們 app 上的 solution 呢?想當然是沒有。於是把 app 改成使用 coordinator 架構這件事就這樣被擺在一旁。

接著過了快一年,剛好公司有一個專案需要從頭開始,這個時候是最適合導入各種你想要、你喜歡的架構的時候。於是選定好 MVVM + Coordinator 當成 project base architecture,開始了 Coordinator 這個不歸路了。

為什麼會想要嘗試 FlowController

這邊直接舉一些當時使用 Coordinator 所遇到的問題

  1. 當我們在使用 navigation controller,遇到要監控 back button click 事件
  2. 要處理 UIWindow
  3. 為什麼需要 .start() 方法
  4. 為什麼要把 coordinator 記在一個 array 裡面
  5. 一個 Coordinator 只能對應一個 View 嗎?

在嘗試了 Coordinator pattern 一年,也就是一個專案從 0 到 1 之後,這些問題是我所遇到,想要嘗試解決、或者找到更好解法的一些問題點。當時剛好看到 Khoa 在 github 上提了這個 issue,讓我想嘗試使用 FlowController,也剛好又有一個新專案可以讓我嘗試 FlowController + MVVM + Combine + Input & Output data binding pattern(是不是腦洞大開啦)。

不過這裡我們還是圍繞在 FlowController 就好,因為你會漸漸發現

不管你用的 architecture 再怎麼華麗花俏,最終還是得回歸 SOLID 原則,好讀好維護,寫別人讀得懂的程式碼才是最重要的。

Architecture 終究只是 architectural pattern

FlowController?

我們可以先看看 FlowController 大致上長怎樣:

struct DependencyContainer: AuthServiceContainer, PhoneServiceContainer, NetworkingServiceContainer,
  LocationServiceContainer, MapServiceContainer, HealthServiceContainer {

  let authService: AuthServiceProtocol
  let phoneService: PhoneService
  let networkingService: NetworkingService
  let locationService: LocationService
  let mapService: MapService
  let healthService: HealthService

  static func make() -> DependencyContainer {
    // Configure and make DependencyContainer here
  }
}

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

  var window: UIWindow?
  var appFlowController: AppFlowController!

  func application(_ application: UIApplication,
                   didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    appFlowController = AppFlowController(
      dependencyContainer: DependencyContainer.make()
    )

    window = UIWindow(frame: UIScreen.main.bounds)
    window?.rootViewController = appFlowController
    window?.makeKeyAndVisible()

    appFlowController.start()

    return true
  }
}

跟 Coordinator 不一樣的點我們可以在這裡就看出來,我們並不需要將 window 丟給 AppCoordinator 去控制,只要將 AppFlowController 加到 window 上,因為他本來就是 UIViewController based,所以保有 UIResponder chain 特性,可以直接顯示 AppFlowController 上第一個 view。

如此一來,我們就不需要將 window 在 coordinator 間互相傳遞,而是只要在 AppFlowController 中控制好我們的 child vc 即可。 (問題點 2 解決)