文章結(jié)構(gòu)
1.什么是Coordinator,它為了解決什么問(wèn)題?
2.Coordinator的優(yōu)點(diǎn)
一、什么是Coordinator,它為了解決什么問(wèn)題?
講這個(gè)問(wèn)題之前我們先看一段我們常用的一段跳轉(zhuǎn)代碼,我們有這樣一個(gè)需求
在訂單列表類SKOrderListViewController點(diǎn)擊相應(yīng)的訂單cell跳轉(zhuǎn)到相應(yīng)的訂單詳情頁(yè)SKDetailViewController。
我們常常會(huì)向下面這么寫:
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點(diǎn)擊事件里面先實(shí)例化接下來(lái)要跳轉(zhuǎn)的SKDetailViewController再配置它所需要的數(shù)據(jù)最后調(diào)用父navigationController推向下一個(gè)頁(yè)面。
這時(shí)候產(chǎn)品過(guò)來(lái)說(shuō)我們說(shuō),這里我們需要根據(jù)用戶角色的不同類型所展示的詳情頁(yè)的明細(xì)不同。
上面的代碼就會(huì)變成下面這樣:
- (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];
}
}
多個(gè)if-else組合判定各種不同的角色類型,再跳轉(zhuǎn)到不同的應(yīng)用界面。
SKOrderListViewController復(fù)用的越多,這樣的判斷代碼就會(huì)也多,在更加復(fù)雜的情況我們甚至需要遍歷navigationController控制器數(shù)組找到指定的VC來(lái)彈出當(dāng)前的控制器或者切換到具體控制器。
VC/ViewModel變的越來(lái)越繁重,界面邏輯跳轉(zhuǎn)的代碼也變的越來(lái)越復(fù)雜。
我們開(kāi)始思考:
1.頁(yè)面跳轉(zhuǎn)這層邏輯到底是不是VC或ViewModel需要關(guān)注的問(wèn)題或者說(shuō)是不是他們的職責(zé),畢竟VC和ViewModel已經(jīng)負(fù)責(zé)了太多的工作?
2.子控制器命令父導(dǎo)航控制器跳轉(zhuǎn)邏輯上是否合理?如果把這種頁(yè)面的跳轉(zhuǎn)相比成我們實(shí)際工作中任務(wù)角色的切換,那么這種方式就像你完成了你的任務(wù)后命令你的boss讓B員工來(lái)處理。是不是應(yīng)該相反你只要關(guān)注你自己的工作內(nèi)容你需要什么工具需要什么材料,讓boss提供,然后完成任務(wù)后告訴boss,而具體后面相關(guān)的任務(wù)Boss指派給誰(shuí),那都不是我們的事情了?
3.上面討論的只是在iphone設(shè)備上,如果這時(shí)項(xiàng)目經(jīng)理提出項(xiàng)目需要同時(shí)兼容ipad ???
4.設(shè)計(jì)模式MVVM -- 按照架構(gòu)規(guī)定,應(yīng)該是由ViewModel負(fù)責(zé)跳轉(zhuǎn)邏輯(當(dāng)然了ViewModel負(fù)責(zé)的工作遠(yuǎn)比這多),而因?yàn)轫?yè)面跳轉(zhuǎn)只能通過(guò)VC,這使得我們不得不在需要跳轉(zhuǎn)的時(shí)候?qū)iewModel跳轉(zhuǎn)事件通知給VC(也就是View層),或者弱引用當(dāng)前的VC, 完成跳轉(zhuǎn),看起來(lái)各種奇怪。同時(shí)也面臨著MVC Controller層一樣的困境,復(fù)用性低。
綜合上面問(wèn)題,我們?nèi)绻烟D(zhuǎn)邏輯剝離出來(lái),單獨(dú)用一個(gè)類(Coordinator)去管理。而VC/ViewModel只需要關(guān)注他需要做的工作需要什么數(shù)據(jù),完成自己的任務(wù)后通知Coordinator,我完成任務(wù)了。Coordinator收到具體的消息后,配置后面需要跳轉(zhuǎn)的VC/ViewModel的數(shù)據(jù),并推向下一個(gè)界面。甚至說(shuō)具體的用戶類型判斷機(jī)型判斷都可以寫到這個(gè)里面,讓Coordinator去處理。這時(shí)VC/ViewModel 就像是樂(lè)高積木中的積木塊,怎么搭,那就完全取決于你了。
這就是Coordinator也有人稱之為FlowControllers。
1.1 Coordinate與VC/ViewModel 之間的關(guān)系
如下所示:

實(shí)線代表: ViewModel/VC 向外提供的接口(屬性或方法集合)
虛線代表: ViewModel/VC 的跳轉(zhuǎn)事件
這里注意到Coordinate 也有相應(yīng)的實(shí)線和虛線,這里我們稍后再講。我們結(jié)合代碼看下Coordinate與VC之間的聯(lián)系:
demo來(lái)自WRAP-UP: IOS MEETUP WITH DEPENDENCY INJECTION AND FLOW CONTROLLERS FlowController章節(jié)
iOS-FlowControllerDemo:




工程功能相對(duì)來(lái)說(shuō)比較簡(jiǎn)單,就不細(xì)分析了,提出幾個(gè)關(guān)鍵的部分我們看下Coordinate與VC之間是怎么聯(lián)系的(ViewModel類似)。
FlowViewController.swift
class FlowViewController {
// MARK: - Properties
var newEvent: Event //控制器所需要數(shù)據(jù)
var navigationController: UINavigationController!//navigationController
var didFinishBlock: ((FlowViewController) -> Void)?//block或者代理傳遞事件給父FlowViewController調(diào)用。
// MARK: - Object Lifecycle
required init(_ navigationController: UINavigationController)
{
self.navigationController = navigationController
self.newEvent = Event()
}
// MARK: - Action 跳轉(zhuǎn)相關(guān)函數(shù)
// Coordinator 初始化頁(yè)
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跳轉(zhuǎn)事件回調(diào)
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提供數(shù)據(jù)接口(在這里是newEvent屬性),并定義跳轉(zhuǎn)方法協(xié)議(SummaryViewControllerDelegate)。Coordinator則負(fù)責(zé)配置SummaryViewController所需的Event數(shù)據(jù),并響應(yīng)SummaryViewController跳轉(zhuǎn)協(xié)議方法。
我們?cè)俳獯鹣律厦嫣岬降臑槭裁碈oordinate也有類似于VC一樣實(shí)線虛線,
看下Coordinate 在整個(gè)工程的結(jié)構(gòu),自然就明白了:

貼幾個(gè)關(guān)鍵性的代碼就不細(xì)講了:
工程地址:
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數(shù)組
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
}
}
最后再引入一個(gè)寫了單元測(cè)試的MVVM-C的工程,方便后期查看
https://github.com/digoreis/ExampleMVVMFlow
2.Coordinator的優(yōu)點(diǎn)
1.VC/ViewModel的復(fù)用性提高
2.VC/ViewModel之間相對(duì)獨(dú)立,單個(gè)VC/ViewModel可測(cè)試性強(qiáng),提高單元測(cè)試的覆蓋率。
3.具體應(yīng)用場(chǎng)景,App跳轉(zhuǎn)情況全部在Coordinator中,VC/ViewModel積木怎么搭完全取決于你。
參考文獻(xiàn):
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
工程參考: