Coordinator
今天要討論的是其中之一成畦,即在解決「數(shù)據(jù)流問(wèn)題」之后硬纤,再對(duì)視圖層的 Navigator 進(jìn)行解耦敞峭,所謂的「Flow Coordinators」。
Coordinator 是 Soroush Khanlou 在一次演講中提出的模式球订,啟發(fā)自?Application Controller Pattern后裸。
先來(lái)看看傳統(tǒng)的作法到底存在什么問(wèn)題。
func
tableView(_tableView:UITableView,didSelectRowAtindexPath:IndexPath) {
letitem=self.dataSource[indexPath.row]
letvc=DetailViewController(item.id)
self.navigationController.pushViewController(vc, animated:true, completion:nil)
}
再熟悉不過(guò)的場(chǎng)景:點(diǎn)擊?ListViewController?中的 table 列表元素冒滩,之后跳轉(zhuǎn)到具體的?DetailViewController微驶。
實(shí)現(xiàn)思路即在?UITableViewDelegate的代理方法中實(shí)現(xiàn)兩個(gè) view 之間的跳轉(zhuǎn)。
看似很和諧开睡。
好因苹,現(xiàn)在我們的業(yè)務(wù)發(fā)展了,需要適配 iPad篇恒,交互發(fā)生了變化扶檐,我們打算使用 popover 來(lái)顯示 detail 信息。
于是胁艰,代碼又變成了這個(gè)樣子:
functableView(_tableView:UITableView,didSelectRowAtindexPath:IndexPath) {
letitem=self.dataSource[indexPath.row]
letvc=DetailViewController(item.id)
if(!Device.isIPad()) {
self.navigationController.pushViewController(vc, animated:true, completion:nil)
}else{
varnc=UINavigationController(rootViewController: vc)
nc.modalPresentationStyle=UIModalPresentationStyle.Popover
varpopover=nc.popoverPresentationController
popoverContent.preferredContentSize=CGSizeMake(500,600)
popover.delegate=self
popover.sourceView=self.view
popover.sourceRect=CGRectMake(100,100,0,0)
presentViewController(nc, animated:true, completion:nil)
}
}
復(fù)制代碼
很快我們感覺到不對(duì)勁款筑,經(jīng)過(guò)理性分析,發(fā)現(xiàn)以下問(wèn)題:
view controller 之間高耦合
ListViewController 沒有良好的復(fù)用性
過(guò)多 if 控制流代碼
副作用導(dǎo)致難以測(cè)試
顯然蝗茁,問(wèn)題的關(guān)鍵在于「解耦」醋虏,看看所謂的 Coordinator 到底起到了什么作用。
先來(lái)看看 Coordinator 主要的職責(zé):
為每個(gè) ViewController 配置一個(gè) Coordinator 對(duì)象
Coordinator 負(fù)責(zé)創(chuàng)建配置 ViewController 以及處理視圖間的跳轉(zhuǎn)
每個(gè)應(yīng)用程序至少包含一個(gè) Coordinator哮翘,可叫做 AppCoordinator 作為所有 Flow 的啟動(dòng)入口
了解了具體概念之后颈嚼,我們用代碼來(lái)實(shí)現(xiàn)一下吧。
不難看出饭寺,Coordinator 是一個(gè)簡(jiǎn)單的概念阻课。因此,它并沒有特別嚴(yán)格的實(shí)現(xiàn)標(biāo)準(zhǔn)艰匙,不同的人或 App 架構(gòu)限煞,在實(shí)現(xiàn)細(xì)節(jié)上也存在差別。
但主流的方式员凝,最多是這兩種:
通過(guò)抽象一個(gè) BaseViewController 來(lái)內(nèi)置 Coordinator 對(duì)象
通過(guò) protocol 和 delegate 來(lái)建立 Coordinator 和 ViewController 之間的聯(lián)系署驻,前者對(duì)后者的「事件方法」進(jìn)行實(shí)現(xiàn)
由于個(gè)人更傾向于低耦合的方案,所以接下來(lái)我們會(huì)采用第二種方案健霹。
事實(shí)上 BaseViewController 在復(fù)雜的項(xiàng)目中旺上,也未必是一種優(yōu)秀的設(shè)計(jì),不少文章采用 AOP 的思路進(jìn)行過(guò)改良糖埋。
好了宣吱,首先我們定義一個(gè) Coordinator 協(xié)議。
protocolCoordinator:class{
funcstart()
varchildCoordinators: [Coordinator] {getset}
}
復(fù)制代碼
Coordinator 存儲(chǔ)了「子 Coordinators」 的引用列表瞳别,防止它們被回收征候,實(shí)現(xiàn)相應(yīng)的列表增減方法杭攻。
extensionCoordinator{
funcaddChildCoordinator(childCoordinator:Coordinator) {
self.childCoordinators.append(childCoordinator)
? ? }
funcremoveChildCoordinator(childCoordinator:Coordinator) {
self.childCoordinators=self.childCoordinators.filter {$0!==childCoordinator }
? ? }
}
復(fù)制代碼
我們說(shuō)過(guò),每個(gè)程序的 Flow 入口是由 AppCoordinator 對(duì)象來(lái)啟動(dòng)的疤坝,在?AppDelegate.swift寫入啟動(dòng)的代碼.
funcapplication(_application:UIApplication,didFinishLaunchingWithOptionslaunchOptions: [UIApplicationLaunchOptionsKey:Any]?) ->Bool{
self.window=UIWindow(frame:UIScreen.main.bounds)
self.window?.rootViewController=UINavigationController()
self.appCoordinator=AppCoordinator(with: window?.rootViewControlleras!UINavigationController)
self.appCoordinator.start()
returntrue
}
復(fù)制代碼
回到我們之前?ListViewController?的例子兆解,我們重新梳理下,看看如何結(jié)合 Coordinator卒煞。假設(shè)需求如下:
如果用戶未登錄狀態(tài)痪宰,顯示登錄視圖
如果用戶登錄了,則顯示主視圖列表
定義?AppCoordinator?如下:
finalclassAppCoordinator:Coordinator{
fileprivateletnavigationController:UINavigationController
init(withnavigationController:UINavigationController) {
self.navigationController=navigationController
}
overridefuncstart() {
if(isLogined) {
showList()
}else{
showLogin()
}
}
}
復(fù)制代碼
那么如何在 AppCoordinator 中創(chuàng)建和配置 view controller 呢畔裕?拿?LoginViewController?為例衣撬。
privatefuncshowLogin() {
letloginCoordinator=LoginCoordinator(navigationController:self.navigationController)
loginCoordinator.delegate=self
loginCoordinator.start()
self.childCoordinators.append(loginCoordinator)
}
extensionAppCoordinator:LoginCoordinatorDelegate{
funcdidLogin(coordinator:AuthenticationCoordinator) {
self.removeCoordinator(coordinator: coordinator)
self.showList()
? ? }
}
復(fù)制代碼
再來(lái)看看如何定義?LoginCoordinator:
importUIKit
protocolLoginCoordinatorDelegate:class{
funcdidLogin(coordinator:LoginCoordinator)
}
finalclassLoginCoordinator:Coordinator{
weakvardelegate:LoginCoordinatorDelegate?
letnavigationController:UINavigationController
letloginViewController:LoginViewController
init(navigationController:UINavigationController) {
self.navigationController=navigationController
self.loginViewController=LoginViewController()
? ? }
overridefuncstart() {
self.showLogin()
? ? }
funcshowLogin() {
self.loginViewController.delegate=self
self.navigationController.show(self.loginViewController, sender:self)
? ? }
}
extensionLoginCoordinator:LoginViewControllerDelegate{
funcdidLogin() {
self.delegate?.didLogin(coordinator:self)
? ? }
}
復(fù)制代碼
正如?UIKit?基于 delegate 的設(shè)計(jì),我們靠這種方式真正實(shí)現(xiàn)了對(duì) view controller 進(jìn)行了解耦扮饶。
同理?LoginViewController?也存在相應(yīng)的?LoginViewControllerDelegate?協(xié)議具练。
importUIKit
protocolLoginViewControllerDelegate:class{
funcdidLogin()
}
finalclassLoginViewController:UIViewController{
weakvardelegate:LoginViewControllerDelegate?
……
}
復(fù)制代碼
這樣,一套基本的 Coordinator 方案就出爐了甜无。當(dāng)然扛点,目前還是非常基礎(chǔ)的功能子集岂丘,我們完全可以在這個(gè)基礎(chǔ)上擴(kuò)展得更加強(qiáng)大陵究。
顯然,一個(gè)成熟的 App 會(huì)存在多樣化的入口奥帘。除了我們一直在討論的 App 內(nèi)跳轉(zhuǎn)之外铜邮,我們還會(huì)遇到以下的路由問(wèn)題:
Deeplink
Push Notifications
Force Touch
常見的,我們很可能需要在手機(jī)上點(diǎn)擊一個(gè)鏈接之后寨蹋,直接鏈接到 app 內(nèi)部的某個(gè)視圖松蒜,而不是 app 正常打開時(shí)顯示的主視圖。
AndreyPanov?的方案解決了這個(gè)問(wèn)題已旧,我們需要對(duì)?Coordinator?再進(jìn)行拓展秸苗。
protocolCoordinator:class{
funcstart()
funcstart(withoption:DeepLinkOption?)
varchildCoordinators: [Coordinator] {getset}
}
復(fù)制代碼
增加了一個(gè)?DeepLinkOption??類型的參數(shù)。這個(gè)有什么用呢运褪?
我們可以在?AppDelegate?中針對(duì)不同的程序喚起方式都用 Coordinator 進(jìn)行啟動(dòng)惊楼。
funcapplication(_application:UIApplication,didFinishLaunchingWithOptionslaunchOptions: [UIApplicationLaunchOptionsKey:Any]?) ->Bool{
letnotification=launchOptions?[.remoteNotification]as?[String:AnyObject]
letdeepLink=buildDeepLink(with: notification)
self.applicationCoordinator.start(with: deepLink)
returntrue
}
funcapplication(_application:UIApplication,didReceiveRemoteNotificationuserInfo: [AnyHashable:Any]) {
letdict=userInfoas?[String:AnyObject]
letdeepLink=buildDeepLink(with: dict)
self.applicationCoordinator.start(with: deepLink)
}
funcapplication(_application:UIApplication,continueuserActivity:NSUserActivity,restorationHandler:@escaping([Any]?) ->Void) ->Bool{
letdeepLink=buildDeepLink(with: userActivity)
self.applicationCoordinator.start(with: deepLink)
returntrue
}
復(fù)制代碼
利用?buildDeepLink?方法對(duì)不同的入口方式判斷輸出相應(yīng)的 flow 類型。
我們對(duì)之前的業(yè)務(wù)需求進(jìn)行相應(yīng)的擴(kuò)展秸讹,假設(shè)存在以下三種不同的 flow 類型:
enumDeepLinkOption{
caselogin// 登錄
casehelp// 幫助中心
casemain// 主視圖
}
復(fù)制代碼
我們來(lái)實(shí)現(xiàn)下?AppCoordinator?中的新?start?方法:
overridefuncstart(withoption:DeepLinkOption?) {
//通過(guò) deeplink 啟動(dòng)
ifletoption=option {
switchoption {
case.login: runLoginFlow()
case.help: runHelpFlow()
default: childCoordinators.forEach { coordinatorin
? ? ? ? ? ? coordinator.start(with: option)
? ? ? ? }
? ? ? ? }
//默認(rèn)啟動(dòng)
}else{
……
? ? }
}
復(fù)制代碼
本文專門介紹了 Coordinator 模式來(lái)對(duì) iOS 開發(fā)中的 navigator 進(jìn)行了深度的解耦胁后。然而當(dāng)今仍沒有權(quán)威標(biāo)準(zhǔn)的解決方案,感興趣的同學(xué)建議去 github 參考下其他更優(yōu)秀的實(shí)踐方案嗦枢。
轉(zhuǎn)載
https://blog.csdn.net/weixin_33768481/article/details/87940599
學(xué)習(xí)使用