2021-11-04

Coordinator

今天要討論的是其中之一成畦,即在解決「數(shù)據(jù)流問(wèn)題」之后硬纤,再對(duì)視圖層的 Navigator 進(jìn)行解耦敞峭,所謂的「Flow Coordinators」。

什么是 Coordinator

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)。

傳統(tǒng)的耦合問(wè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è)試

Coordinator 如何改進(jìn)

顯然蝗茁,問(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ù)制代碼

總結(jié)

本文專門介紹了 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í)使用

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市屯断,隨后出現(xiàn)的幾起案子文虏,更是在濱河造成了極大的恐慌侣诺,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件氧秘,死亡現(xiàn)場(chǎng)離奇詭異年鸳,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)丸相,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門搔确,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)祠饺,“玉大人乓旗,你說(shuō)我怎么就攤上這事沉噩∨褐模” “怎么了懊渡?”我有些...
    開封第一講書人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵初肉,是天一觀的道長(zhǎng)析校。 經(jīng)常有香客問(wèn)我皿哨,道長(zhǎng)映琳,這世上最難降的妖魔是什么机隙? 我笑而不...
    開封第一講書人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮萨西,結(jié)果婚禮上有鹿,老公的妹妹穿的比我還像新娘。我一直安慰自己谎脯,他們只是感情好葱跋,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著穿肄,像睡著了一般年局。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上咸产,一...
    開封第一講書人閱讀 49,950評(píng)論 1 291
  • 那天矢否,我揣著相機(jī)與錄音,去河邊找鬼脑溢。 笑死僵朗,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的屑彻。 我是一名探鬼主播验庙,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼社牲!你這毒婦竟也來(lái)了粪薛?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤搏恤,失蹤者是張志新(化名)和其女友劉穎违寿,沒想到半個(gè)月后湃交,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡藤巢,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年搞莺,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片掂咒。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡才沧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出绍刮,到底是詐尸還是另有隱情温圆,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布录淡,位于F島的核電站捌木,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏嫉戚。R本人自食惡果不足惜刨裆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望彬檀。 院中可真熱鬧帆啃,春花似錦、人聲如沸窍帝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)坤学。三九已至疯坤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間深浮,已是汗流浹背压怠。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留飞苇,地道東北人菌瘫。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像布卡,于是被迫代替她去往敵國(guó)和親雨让。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容