文章结构
1.什么是Coordinator,它为了解决什么问题?
2.Coordinator的优点
一、什么是Coordinator,它为了解决什么问题?
讲这个问题之前我们先看一段我们常用的一段跳转代码,我们有这样一个需求
在订单列表类SKOrderListViewController
点击相应的订单cell跳转到相应的订单详情页SKDetailViewController
。
我们常常会向下面这么写:
SKOrderListViewController.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
id object = [self.dataSource objectAtIndexPath:indexPath];
SKDetailViewController *detailViewController = [[SKDetailViewController alloc] initWithDetailObject:object];
[self.navigationController pushViewController:detailViewController animated:YES];
}
在tableview 代理里的cell点击事件里面先实例化接下来要跳转的SKDetailViewController
再配置它所需要的数据
最后调用父navigationController推向下一个页面
。
这时候产品过来说我们说,这里我们需要根据用户角色的不同类型所展示的详情页的明细不同。
上面的代码就会变成下面这样:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
id object = [self.dataSource objectAtIndexPath:indexPath];
if(user.role == userRoleNor){
SKDetailViewController *detailViewController = [[SKDetailViewController alloc] initWithDetailObject:object];
[self.navigationController pushViewController:detailViewController animated:YES];
}else if(user.role == userRoleVip){
SKVipDetailViewController *detailViewController = [[SKVipDetailViewController alloc] initWithDetailObject:object];
[self.navigationController pushViewController:detailViewController animated:YES];
}else if(user.role == userRoleMannage){
SKManDetailViewController *detailViewController = [[SKManDetailViewController alloc] initWithDetailObject:object];
[self.navigationController pushViewController:detailViewController animated:YES];
}
}
多个if-else组合判定各种不同的角色类型,再跳转到不同的应用界面。
SKOrderListViewController
复用的越多,这样的判断代码就会也多,在更加复杂的情况我们甚至需要遍历navigationController
控制器数组找到指定的VC来弹出当前的控制器或者切换到具体控制器。
VC/ViewModel变的越来越繁重,界面逻辑跳转的代码也变的越来越复杂。
我们开始思考:
1.页面跳转这层逻辑到底是不是VC或ViewModel需要关注的问题或者说是不是他们的职责,毕竟VC和ViewModel已经负责了太多的工作?
2.子控制器命令父导航控制器跳转逻辑上是否合理?如果把这种页面的跳转相比成我们实际工作中任务角色的切换,那么这种方式就像你完成了你的任务后命令你的boss让B员工来处理。是不是应该相反你只要关注你自己的工作内容你需要什么工具需要什么材料,让boss提供,然后完成任务后告诉boss,而具体后面相关的任务Boss指派给谁,那都不是我们的事情了?
3.上面讨论的只是在iphone设备上,如果这时项目经理提出项目需要同时兼容ipad ?
4.设计模式MVVM -- 按照架构规定,应该是由ViewModel负责跳转逻辑(当然了ViewModel负责的工作远比这多),而因为页面跳转只能通过VC,这使得我们不得不在需要跳转的时候将ViewModel跳转事件通知给VC(也就是View层),或者弱引用当前的VC, 完成跳转,看起来各种奇怪。同时也面临着MVC Controller层一样的困境,复用性低。
综合上面问题,我们如果把跳转逻辑剥离出来,单独用一个类(Coordinator)去管理。而VC/ViewModel只需要关注他需要做的工作需要什么数据,完成自己的任务后通知Coordinator,我完成任务了。Coordinator收到具体的消息后,配置后面需要跳转的VC/ViewModel的数据,并推向下一个界面。甚至说具体的用户类型判断机型判断都可以写到这个里面,让Coordinator去处理。这时VC/ViewModel 就像是乐高积木中的积木块,怎么搭,那就完全取决于你了。
这就是Coordinator也有人称之为FlowControllers。
1.1 Coordinate与VC/ViewModel 之间的关系
如下所示:
实线代表: ViewModel/VC 向外提供的接口(属性或方法集合)
虚线代表: ViewModel/VC 的跳转事件
这里注意到Coordinate 也有相应的实线和虚线,这里我们稍后再讲。我们结合代码看下Coordinate与VC之间的联系:
demo来自WRAP-UP: IOS MEETUP WITH DEPENDENCY INJECTION AND FLOW CONTROLLERS FlowController章节
iOS-FlowControllerDemo:
工程功能相对来说比较简单,就不细分析了,提出几个关键的部分我们看下Coordinate与VC之间是怎么联系的(ViewModel类似)。
FlowViewController.swift
class FlowViewController {
// MARK: - Properties
var newEvent: Event //控制器所需要数据
var navigationController: UINavigationController!//navigationController
var didFinishBlock: ((FlowViewController) -> Void)?//block或者代理传递事件给父FlowViewController调用。
// MARK: - Object Lifecycle
required init(_ navigationController: UINavigationController)
{
self.navigationController = navigationController
self.newEvent = Event()
}
// MARK: - Action 跳转相关函数
// Coordinator 初始化页
func start()
{
showLocationScreen()
}
func showLocationScreen()
{
let storyboard = UIStoryboard(name: "Location", bundle: nil)
if let controller = storyboard.instantiateInitialViewController() as? LocationViewController {
controller.delegate = self
navigationController.pushViewController(controller, animated: true)
}
}
.....................
func showSummaryScreen()
{
let storyboard = UIStoryboard(name: "Summary", bundle: nil)
if let controller = storyboard.instantiateInitialViewController() as? SummaryViewController {
controller.delegate = self
controller.newEvent = newEvent
navigationController.pushViewController(controller, animated: true)
}
}
func finish()
{
navigationController.popToRootViewController(animated: true)
didFinishBlock!(self)
}
// MARK: - delegate VC跳转事件回调
extension FlowViewController: SummaryViewControllerDelegate {
func controllerDidConfirm(controller: SummaryViewController) {
finish()
}
func controllerDidCancel(controller: SummaryViewController) {
}
}
SummaryViewController.swift
import UIKit
protocol SummaryViewControllerDelegate: class {
func controllerDidConfirm(controller: SummaryViewController)
func controllerDidCancel(controller: SummaryViewController)
}
class SummaryViewController: UIViewController {
// MARK: - Data
var newEvent: Event!
func configureView() {
lblLocationValue.text = newEvent.location
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
lblDateValue.text = dateFormatter.string(from: newEvent.date!)
}
// MARK: - Action
func doneAction() {
didConfirm()
}
SummaryViewController 向Coordinator提供数据接口(在这里是newEvent属性),并定义跳转方法协议(SummaryViewControllerDelegate)。Coordinator则负责配置SummaryViewController所需的Event数据,并响应SummaryViewController跳转协议方法。
我们再解答下上面提到的为什么Coordinate也有类似于VC一样实线虚线,
看下Coordinate 在整个工程的结构,自然就明白了:
贴几个关键性的代码就不细讲了:
工程地址:
mvvmc-demo
AppCoordinator.swift
class AppCoordinator: Coordinator
{
fileprivate let AUTHENTICATION_KEY: String = "Authentication"
fileprivate let LIST_KEY: String = "List"
var window: UIWindow
var coordinators = [String:Coordinator]()//子Coordinator数组
init(window: UIWindow)
{
self.window = window
}
func start()
{
if isLoggedIn {
showList()
} else {
showAuthentication()
}
}
func showList()
{
let listCoordinator = ListCoordinator(window: window)
coordinators[LIST_KEY] = listCoordinator
listCoordinator.delegate = self
listCoordinator.start()
}
func showAuthentication()
{
let authenticationCoordinator = AuthenticationCoordinator(window: window)
coordinators[AUTHENTICATION_KEY] = authenticationCoordinator
authenticationCoordinator.delegate = self
authenticationCoordinator.start()
}
}
extension AppCoordinator: AuthenticationCoordinatorDelegate
{
var isLoggedIn: Bool {
return false;
}
func authenticationCoordinatorDidFinish(authenticationCoordinator: AuthenticationCoordinator)
{
coordinators[AUTHENTICATION_KEY] = nil
showList()
}
}
AppDelegate.swift
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate
{
var window: UIWindow?
var appCoordinator: AppCoordinator!
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool
{
window = UIWindow()
appCoordinator = AppCoordinator(window: window!)
appCoordinator.start()
window?.makeKeyAndVisible()
return true
}
}
最后再引入一个写了单元测试的MVVM-C的工程,方便后期查看
https://github.com/digoreis/ExampleMVVMFlow
2.Coordinator的优点
1.VC/ViewModel的复用性提高
2.VC/ViewModel之间相对独立,单个VC/ViewModel可测试性强,提高单元测试的覆盖率。
3.具体应用场景,App跳转情况全部在Coordinator中,VC/ViewModel积木怎么搭完全取决于你。
参考文献:
Improve your iOS Architecture with FlowControllers
Coordinators Redux
Coordinator and FlowController
An iOS Coordinator Pattern
MVVM-C A simple way to navigate
MVVM with Flow Controller
MVVM and Tests
工程参考:
mvvmc-demo
iOS-FlowControllerDemo
ExampleMVVMFlow