ADCoordinator
示例
要运行示例项目,请先克隆仓库,然后在 Example 目录中首先运行 pod install
要求
ADCoordinator 使用 Swift 5.1 编写,兼容 iOS 10.0 以上。
安装
ADCoordinator 通过 CocoaPods 提供。要安装它,只需在 Podfile 中添加以下行
pod 'ADCoordinator'
架构
每个协调器都是通过与它“不拥有”的图形上下文实例化的。例如,协调器可以用 UIWindow
或 UINavigationController
初始化。
class ApplicationCoordinator: Coordinator {
private unowned var navigationController: UINavigationController
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
}
注意:这里的unowed
是必须的,以避免保留UIKit对象。
然后,协调器负责在这个图形上下文中创建和显示视图控制器。
按照惯例,我们使用名为start
的方法告诉协调器创建它的第一个视图控制器
class ApplicationCoordinator: Coordinator, MasterViewControllerDelegate {
...
// MARK: - Public
func start() {
let viewController = MasterViewController()
// the view controller can notify the coordinator when it needs to navigate
viewController.delegate = self
// display the first view controller in the navigation controller
navigationController.pushViewController(viewController, animated: false)
// Automatic memory management
bindToLifecycle(of: viewController)
}
}
外部世界负责在屏幕上创建和显示图形上下文。
在这种情况下,AppDelegate
将创建一个navigationController
并将其设置为其窗口的rootViewController
。
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var applicationCoordinator: ApplicationCoordinator?
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let window = UIWindow(frame: UIScreen.main.bounds)
self.window = window
// Create the graphical context
let navigationController = UINavigationController()
// Create the coordinator with the graphical context
applicationCoordinator = ApplicationCoordinator(navigationController: navigationController)
// Let the coordinator display its content
applicationCoordinator?.start()
// Present the graphical context on screen
window.rootViewController = navigationController
window.makeKeyAndVisible()
return true
}
}
导航
一旦我们想要改变屏幕上的视图控制器,我们就询问协调器。不允许在协调器外部进行导航。
我们可以水平地推入视图控制器(即在相同的导航堆栈上)或垂直地显示视图控制器(即在另一个导航控制器上,该控制器位于顶部)。
水平方向
当协调器接收到屏幕上视图控制器的导航回调时,最简单的方法就是创建一个新的视图控制器并将其推入导航堆栈。
class ApplicationCoordinator: Coordinator, MasterViewControllerDelegate {
...
// MARK: - MasterViewControllerDelegate
func masterViewControllerDidRequestPush() {
let detailViewController = DetailViewController()
navigationController.pushViewController(detailViewController, animated: true)
}
}
垂直方向
在这种情况下,这意味着创建一个新的图形上下文(通常是导航控制器)并将其显示在顶部。
经验法则:每次调用方法UIViewController.present(_:animated:completion)
时都创建一个新的协调器。
class DetailCoordinator: Coordinator {
private unowned var navigationController: UINavigationController
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
// MARK: - Public
func start() {
let viewController = DetailViewController()
navigationController.pushViewController(viewController, animated: false)
// Automatic memory management
bindToLifecycle(of: viewController)
}
}
这始终是相同的概念,协调器获得了一个它不拥有的图形上下文并在这个上下文中执行操作。这是创建图形上下文和子协调器的主协调器。
class ApplicationCoordinator: Coordinator, MasterViewControllerDelegate {
...
// MARK: - MasterViewControllerDelegate
func masterViewControllerDidRequestPresent() {
// The new graphical context for the child coordinator
let navigationController = UINavigationController()
// Create the child coordinator
let coordinator = DetailCoordinator(navigationController: navigationController)
// Add child to the tree of coordinators
addChild(coordinator)
// Start the coordinator
coordinator.start()
// Present the new graphical context on screen
self.navigationController.present(navigationController, animated: true)
}
}
内存管理
基础知识
每次创建新的协调器时,都必须由另一个对象保留。根协调器由 AppDelegate
保留,每个协调器都存储一个子协调器数组。协调器可以通过调用 addChild
方法保留子协调器,并通过 removeChild
方法从 children
数组中移除一个子协调器。
class Coordinator {
private(set) var children: [Coordinator] = []
private(set) weak var parent: Coordinator?
func addChild(_ coordinator: Coordinator) { ... }
func removeChild(_ coordinator: Coordinator) { ... }
}
自动移除子项
从 iOS 13 开始,引入了一种新的模态展示方式。该展示方式并不覆盖整个屏幕,更重要的是,用户可以向下滑动来关闭模态框,而无需回调到视图控制器。
我们之前看到,每个展示的模态都导致创建一个新协调器。一旦模态被关闭,相关的协调器应该被释放。如果用户点击按钮,将动作与该按钮关联,视图控制器可以将意图转发给正确的协调器,调用 removeChild
。但是如果用户在 iOS 13 上向下滑动模态框,不会触发任何动作。
为了解决此问题,每个协调器都应该调用 bindToLifecyle(of:)
,并将它所绑定的视图控制器作为参数传递。
class DetailCoordinator: Coordinator {
...
// MARK: - Public
func start() {
let viewController = DetailViewController()
navigationController.pushViewController(viewController, animated: false)
bindToLifecycle(of: viewController)
}
}
此方法将观察 viewController
的生命周期,一旦它被释放,将要求父协调器自动调用 removeChild
。这样,子协调器将在它的图形上下文释放时一起被释放。
注意:相同的模式可以应用于水平导航中,当你想要将重复的代码提取到一个协调器中时。当用户在导航栏中点击返回按钮时,你的应用不会触发任何动作。因此,你必须使用 bindToLifecycle(of:)
来观察你想要返回的视图控制器。
bindToLifecycle(of:)
使用 选择正确的对象传递给 bindToLifecycle(of:)
是良好内存管理的关键。如果协调器以导航控制器初始化,你应该观察协调器推入堆栈的第一个视图控制器。这样
- 当导航控制器被释放时,视图控制器也会被释放,协调器会得到通知
- 如果视图控制器不是堆栈中的第一个,并且用户执行返回操作,视图控制器将被释放,协调器再次得到通知
- 如果你想更改导航控制器堆栈,你可以使用另一个视图控制器来调用
bindToLifecycle(of:)
,以移除之前的观察者并创建一个新的观察者。
作者
许可证
ADCoordinator遵循MIT许可证。详细信息请参阅LICENSE文件。