CoordinatorKitSwift 1.1.3

CoordinatorKitSwift 1.1.3

测试测试
语言语言 SwiftSwift
许可证 MIT
发布最后发布2017年12月
SwiftSwift版本4
SPM支持SPM

Brendan Conron 维护。



  • 作者
  • startupthekid

CoordinatorKit

什么是 CoordinatorKit?

CoordinatorKit 是一个用 Swift 编写的 iOS 架构框架,提供了用于构建应用程序的替代设计模式(协调器模式)。简单、健壮且可扩展,CoordinatorKit 允许您简化应用程序的架构。

协调器

协调器是 PONSOs(平凡的旧 NSObjects),与任何其他对象没有区别。将协调器想象成没有视图的视图控制器。协调器是真正的控制器对象(有时被称为导演)可以承担原本由 UIViewController 所承担的各种责任,我们现在可以将其视为视图层的一部分。

协调器到底是什么样子?

class AppDelegate {
  let coordinator = AppCoordinator()

  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
    coordinator.start()
  }
}

class AppCoordinator: Coordinator {

  public override func start() {
    super.start()
    if session.isUserLoggedIn {
      start(coordinator: MainExperienceCoordinator())
    } else {
      start(coordinator: LoginCoordinator())
    }
  }

}

将每个协调器想象成一个封装了一个或多个体验的容器。每个应用程序的根都是一个由 AppDelegate 保留的 AppCoordinator。根据用户的状态,应用程序协调器可以派生子协调器 (LoginCoordinatorMainExperienceCoordinator)。注意,我们还没有接触到任何视图代码,协调器只包含纯业务逻辑。这是使用协调器的关键优势之一;它们允许您将视图与一切其他内容分离。有趣的是,协调器不仅限于拥有一个活动的子协调器。一个类似 UISplitViewController 的协调器类可能同时有多个活动的子项,每个子项可能管理着自己的子项和视图控制器。最后,你将得到一个协调器的树,其中 ApplicationCoordinator 位于顶部,而子项从那里分支出来。

子项

为了向协调器添加或删除子项,我们提供了一组便利方法

/// Start a child coordinator.
///
/// This method should *always* be used rather than calling `coordinator.start()`
/// directly. Starting a child coordinator has two important side-effects:
/// 1) The parent coordinator adds itself as a delegate of the child.
/// 2) The coordinator gets inserted into the set of children.
///
/// - Parameter coordinator: Coordinator to start.
public final func start<C: Coordinator>(coordinator: C) {
    guard !hasChild(coordinator) else { return }
    coordinator.delegate += self
    children.insert(coordinator)
    coordinator.start()
}

/// Stops the given child coordinator.
///
/// This method *must* be used instead of calling `coordinator.stop()` directly.
/// Stopping a child coordinator has two important side-effects:
/// 1) The parent removes itself as a delegate.
/// 2) The coordinator is removed from the list of children.
///
/// - Parameter coordinator: Coordinator to stop.
public final func stop<C: Coordinator>(coordinator: C) {
    guard hasChild(coordinator) else { return }
    coordinator.delegate -= self
    children.remove(coordinator)
    coordinator.stop()
}

/// Pauses the given child coordinator.
///
/// This method is a wrapper function for convenience and consistency.
///
/// - Parameter coordinator: Coordinator to pause.
public final func pause<C: Coordinator>(coordinator: C) {
    guard hasChild(coordinator) else { return }
    guard !coordinator.isPaused else { return }
    coordinator.pause()
}

/// Resumes the given child coordinator.
///
/// This method is a wrapper function for convenience and consistency.
///
/// - Parameter coordinator: Coordinator to resume.
public final func resume<C: Coordinator>(coordinator: C) {
    guard hasChild(coordinator) else { return }
    guard coordinator.isPaused else { return }
    coordinator.resume()
}

使用这些方法添加和删除子项非常重要,不要直接调用 coordinator.start() 或其他任何方法。唯一可以直接在其上调用它们的是 AppCoordinator 对象。

状态

协调器有三个状态,分别是 inactive(不活跃)、paused(暂停)和 active(激活)。可以使用以下四种方法之一来操作协调器的状态

  • start - 启动协调器并开始任何工作
  • stop - 停止协调器并停止所有工作。
  • pause - 暂停协调器并限制任何活动的工作。
  • resume - 恢复并继续工作。

这四种方法都可以被继承,并提供挂钩以进入协调器生命周期执行工作和采取任何必要的操作。例如,我们上面示例中的LoginCoordinator可能看起来像这样:

class LoginCoordinator: Coordinator {
  public override func start() {
    super.start()
    if session.hasCachedEmail {
      viewController.loginTextField.text = session.cachedEmail
    }
  }
}

注意:你必须在每个重写的方法中首先调用超类,即 super.start()

场景协调器

场景协调器是Coordinator的一个特殊子类。场景协调器代表你的应用中的一个单独的屏幕或视图。它们与视图控制器之间保持1:1的关系。

场景协调器如下所示:

open class SceneCoordinator<Controller: UIViewController>: Coordinator {

  public var rootViewController: Controller

  required public init() {
    rootViewController = Controller()
    super.init()
  }

}

场景协调器将是你应用程序中大多数用例的一部分。还记得之前的LoginCoordinator吗?一个更正确的实现可能看起来像这样:

class LoginCoordinator: SceneCoordinator<LoginViewController> {
  public override func start() {
    super.start()
    if session.hasCachedEmail {
      rootViewController.loginTextField.text = session.cachedEmail
    }
  }
}

现在登录协调器自动具有对LoginViewController的引用,由于SceneCoordinator中的泛型约束,rootViewController的类型为LoginViewController,而不是UIViewController,这意味着在每个专业子类中,自动补全按预期工作。

通信

你可能想知道,如果你还没有失去耐心,关于孩子们如何与他们的父母沟通。父代 -> 子代的关系是清晰的,父母根据内部业务逻辑启动子代协调器,但反过来就比较模糊。登录协调器如何通知应用程序协调器成功的登录,以便它可以被关闭并且显示主要体验?答案是:委派。

协调器包含一个delegate属性,可以用于在整个系统中传递消息。

/// Coordinator delegate protocol. Used to notify listeners
/// of state changes in the coordinator.
public protocol CoordinatorDelegate: class {

    /// Notifies the delegate that the coordinator has been started.
    ///
    /// - Parameter coordinator: Coordinator that was started.
    func coordinatorDidStart(_ coordinator: Coordinator)

    /// Notifies the delegate that the coordinator has been stopped.
    ///
    /// - Parameter coordinator: Coordinator that was stopped.
    func coordinatorDidStop(_ coordinator: Coordinator)

    /// Notifies the delegate that the coordinator has been paused.
    ///
    /// - Parameter coordinator: Coordinator that was paused.
    func coordinatorDidPause(_ coordinator: Coordinator)

    /// Notifies the delegate that the coordinator has been resumed.
    ///
    /// - Parameter coordinator: Coordinator that was resumed.
    func coordinatorDidResume(_ coordinator: Coordinator)

}

Coordinator基础类自动符合此协议,并提供了空实现,您可以重写这些实现并监听事件。SceneCoordinator也有一个代理,SceneCoordinatorDelegate

/// Delegate protocol for SceneCoordinators. It extends the `CoordinatorDelegate` protocol.
public protocol SceneCoordinatorDelegate: CoordinatorDelegate {

    /// Notifies the delegate that the given coordinator is requesting to be dismissed.
    ///
    /// - Parameter coordinator: Coordinator that's requesting dismissal.
    func coordinatorDidRequestDismissal<C: UIViewController>(_ coordinator: SceneCoordinator<C>)

}

注意这里如何在SceneCoordinatorDelegate中符合CoordinatorDelegate。这意味着所有符合〈code>SceneCoordinatorDelegate的(默认情况下,所有场景协调器都是这样)也符合CoordinatorDelegate。这个简单但强大的区分意味着可以扩展和专业化delegate属性,而无需额外的属性。

难以理解?当然了。这里有一个示例可能有助于澄清问题。还记得我们那位老朋友LoginCoordinator吗?这是它与其父级通信的方式。

public protocol LoginCoordinatorDelegate: SceneCoordinatorDelegate {

  func coordinatorDidLoginSuccessfully()

  func coordinatorDidFailToLogin(error: Error)

}

AppCoordinator随后将自己添加为代理并实现这些方法以了解何时显示错误模态或主要体验。幸运的是,每次协调器使用〈code>start(coordinator: coordinator)调用启动子协调器时,它都会自动将自身添加为子级的代理。

也值得注意,协调器不会使用通常的1:1代理关系。相反,代理被包裹在MulticastDelegate结构体中,这允许多个监听器。让你有时间吸收这一点。这意味着,每个协调器都可以有从0到n个观察者,这立即使得整个系统变得更加灵活。一个协调器可以监听并记录事件,而另一个协调器可以显示和关闭视图。

要将自身添加为代理

let coordinator = MyFunCoordinator()
coordinator.delegate += self

要删除

coordinator.delegate -= self

当你需要向代理调用方法以通知所有监听器时,使用=>运算符来提供一个在所有代理上执行的闭包。

  delegate => { $0.coordinatorDidLoginSuccessfully() }

接下来做什么

协调器,虽然概念很难理解,但在做了MVC之后却是一种简单且可扩展的架构。网上有一些很棒的文章,我推荐阅读以充实你对协调器如何工作的理解。

此外,还有一个来自 NSSpain 的精彩的视频,视频中近期的协调员促进者 Soroush Khanlou 谈论了他如何在应用程序中使用协调器。

贡献

有问题?创建一个 issues!有想法?发起一个 pull request!

如果你喜欢这个库,请⭐️它!

干杯🍻