摘要:每個(gè)都定義了可以監(jiān)聽它們的行為的接口如果需要支持多個(gè)設(shè)備和不同的展示方式,程序中會(huì)有其他類,沒有意大利面條的代碼。使用這樣的架構(gòu)的好處是顯而易見的界面之間沒有依賴關(guān)系。更干凈的代碼,我看到的唯一意大利面條代碼是我做的。
引言
問題開發(fā)iOS應(yīng)用時(shí),現(xiàn)在更應(yīng)該避免在一個(gè)視圖控制器中直接展示其他視圖控制器。
為什么?
現(xiàn)代應(yīng)用程序通常需要需要支持以多種方式展示相同的視圖控制器。例如,在iPhone上你 push 一個(gè)新的視圖控制器,但是在 iPad 上,你會(huì)把它嵌入另一個(gè)視圖控制器或者用 popover 展示出來。
另外,很多情況下,你可能想在不同的情景中重用同一個(gè)視圖控制器。如 UIImagePickerController 可以在多個(gè)地方以不同的方式展示出來。
視圖控制器應(yīng)該不依賴于他們的展示樣式,這就是 SizeClasses 出現(xiàn)的原因之一。
如果你從其他 VCs / ViewModels 展示視圖控制器,你將寫出來一堆if語句,你的代碼將變成條件大面條(big spaghetti of conditions)。
我作為一個(gè)顧問,經(jīng)常需要參與審查項(xiàng)目,并幫助團(tuán)隊(duì)制訂更干凈的解決方案。
我看到過很多的意大利面條代碼。下面這個(gè)就是相當(dāng)糟糕的一個(gè)例子,但是還沒有接近我見過最差的:
func doneButtonTapped() { let vc = NextViewController(prepareNeccesaryState()) if Device.isIPad() { navigationController.pushViewController(vc, animated: true, completion: nil) } else { var nav = UINavigationController(rootViewController: vc) nav.modalPresentationStyle = UIModalPresentationStyle.Popover var popover = nav.popoverPresentationController popoverContent.preferredContentSize = CGSizeMake(500, 600) popover.delegate = self popover.sourceView = self.view popover.sourceRect = CGRectMake(100, 100, 0, 0) presentViewController(nav, animated: true, completion: nil) } }
這個(gè)幼稚的實(shí)現(xiàn)存在很多問題:
不必要的依賴 —— 一個(gè)視圖控制器不需要知道另一個(gè)
可重用性差
面條代碼,如果你的應(yīng)用需要以不同的方式展示視圖 - 你需要寫大量的控制流。
單例是誘人的,因?yàn)樗鼈冏屇愀菀拙帉懘a。
測(cè)試更難,你的VC / VM會(huì)有很多的副作用。
我們?cè)鯓硬拍芙鉀Q這個(gè)問題? 清理你的 ViewControllers / ViewModels這可以應(yīng)用到 MVVM,MVC 和許多其他的常見模式。當(dāng)我談到 VC / VM,思考一下你現(xiàn)在正在使用的那一個(gè)。
讓我們使用代理或者基于 block 的接口,而不是對(duì)相關(guān)的控制器硬編碼,來擺脫所有的依賴關(guān)系。
class MyViewController { let onDone = (Void -> Void)? func doneButtonTapped() { onDone?(prepareNeccesaryState()) } }
ViewController / ViewModel 應(yīng)該:
不能引用其他界面
不使用任何 UIKit presentation 類或類似 UINavigationController 或 presentViewController 的方法
有允許其他的對(duì)象通過注冊(cè)來獲知這個(gè)功能正在運(yùn)行的接口,例如,代理或 block
不引用任何單例,稍后你會(huì)看到用我的做法是如何容易實(shí)現(xiàn)這一要求的。
這個(gè)時(shí)候,我們已經(jīng)提高了可測(cè)試性,因?yàn)槲覀儸F(xiàn)在可以測(cè)試我們的接口是否被觸發(fā)了,且無需副作用。偽代碼:
let vc = createVC() var executed = false vc.onDone = { executed = true } //! add code here to trigger done state expect(executed).toEventually(beTruthy())
但是,我們?nèi)绾螀f(xié)調(diào)我們的應(yīng)用程序視圖控制器?
介紹 FlowControllers一個(gè) FlowController 是一個(gè)簡單的對(duì)象,它將管理你的應(yīng)用程序的一部分,我喜歡把它看成用例的一個(gè)子集。
FlowController 的三個(gè)主要角色是:
為視圖控制器配置特定上下文 - 例如分別為從應(yīng)用 CreatePost 界面彈出的 ImagePicker 和改變用戶頭像時(shí)彈出的設(shè)置不同的配置
監(jiān)聽每個(gè) ViewController 中的重要事件,并用來協(xié)調(diào)它們之間的流程。
為視圖控制器提供它需要的東西,從而移除VC中的單例
func configureProgramsViewController(viewController: ProgramsViewController, navigationController: UINavigationController) { viewController.state = state viewController.addProgram = { [weak self] barButton in guard let strongSelf = self else { return } let createVC = R.storyboard.createProgram.initialViewController! strongSelf.configureCreateProgramViewController(createVC, navigationController: navigationController) navigationController.pushViewController(createVC, animated: true) } }
常見帶有 FlowControllers 的應(yīng)用架構(gòu)像這樣:
每個(gè)應(yīng)用程序都有至少一個(gè) FlowController,Root FlowController 由 AppDelegate 創(chuàng)建。
*實(shí)際上是 AppDelegate 中的一個(gè) ApplicationController 創(chuàng)建了它,作為一個(gè)經(jīng)驗(yàn)法則,你永遠(yuǎn)不應(yīng)該引??用你的AppDelegate,永遠(yuǎn)。*
每個(gè) FlowController 可以有子控制器。
*如果您的應(yīng)用程序具有可被看作是一個(gè)整體,需要多個(gè)屏幕的用戶故事的一些重要的子集(如創(chuàng)建新的鍛煉計(jì)劃),那么你可以為那一部分創(chuàng)建一個(gè)新的子控制器,并從主控制器展示它。*
VC / VM 不知道其他 VC / VM。
*這意味著他們可以在任何地方重復(fù)使用,如果一個(gè)步驟是從導(dǎo)入用戶照片庫里的東西,你可以在應(yīng)用程序的不同部分重復(fù)使用這段代碼,例如,EditProfile可以使用相同的選擇器選擇用戶頭像。*
流量控制器配置和協(xié)調(diào)不同的界面。
每個(gè) VC / VM 都定義了可以監(jiān)聽它們的行為的接口
如果需要支持多個(gè)設(shè)備和不同的展示方式,程序中會(huì)有其他 FlowController 類,沒有意大利面條的代碼。
這個(gè)想法最初是 Jim 和 Sami 一年前介紹給我的,我們經(jīng)常使用它。
盡管我們的應(yīng)用劇烈改變了3次,我們的架構(gòu)都輕松地應(yīng)對(duì)了,我們能夠重復(fù)使用大量的代碼,也有不少控制器不需要任何改變。
使用這樣的架構(gòu)的好處是顯而易見的:
界面之間沒有依賴關(guān)系。
高復(fù)用率。
更簡單的代碼注入并移除了單例。
更干凈的代碼,我看到的唯一意大利面條代碼是我做的。
以更有表現(xiàn)力的方式來導(dǎo)航。
能夠在共用大多數(shù)代碼時(shí)對(duì)不同的設(shè)備編寫不同的流。
可以輕松分離測(cè)試每個(gè) VC / VM,因?yàn)橐磺卸伎梢宰⑷?。沒有必要子類化。
現(xiàn)在,在一些架構(gòu)中也有類似的概念,如 VIPER 有路由器。但它們通常是很復(fù)雜的,需要大量的前期成本,以適配到現(xiàn)有的應(yīng)用程序中。
這個(gè)方法最棒的地方是它很簡單直觀,立刻就可以(在現(xiàn)有的項(xiàng)目中)使用它,無需等待新項(xiàng)目。它在小型和大型項(xiàng)目的效果一樣好。
不管你使用 MVVM,MVC 還是其他模式,如果應(yīng)用中存在界面跳轉(zhuǎn),不妨試一試。
原文作者:Krzysztof Zab?ocki
原文鏈接:http://merowing.info/2016/01/improve-your-ios-architecture-with-flowcontrollers/
翻譯自 MaxLeap 團(tuán)隊(duì)_UX成員:Alex Sun
翻譯首發(fā)鏈接:https://blog.maxleap.cn/archives/879
商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明作者信息與出處。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/11749.html
摘要:移動(dòng)精英開發(fā)社群的第期,也是圍繞架構(gòu)這個(gè)話題進(jìn)行討論。本次我們希望結(jié)合實(shí)際開發(fā)中遇到的問題,來聊聊移動(dòng)端的架構(gòu)設(shè)計(jì)。這樣的模式改進(jìn)一些,可能會(huì)更適合移動(dòng)端架構(gòu)。潘衛(wèi)杰之前我們公司移動(dòng)端的大項(xiàng)目就是插座式開發(fā)的,批量出各個(gè)行業(yè)的。 此前,58 同城的技術(shù)委員會(huì)執(zhí)行主席沈劍在 OneAPM 的技術(shù)公開課上分享過一個(gè)主題,「好的架構(gòu)不是設(shè)計(jì)出來的,而是演技出來的」。因?yàn)閷?duì)很多創(chuàng)業(yè)公司而言,隨...
閱讀 727·2021-11-22 13:52
閱讀 1537·2021-09-27 13:36
閱讀 2840·2021-09-24 09:47
閱讀 2199·2021-09-22 15:48
閱讀 3612·2021-09-22 15:39
閱讀 1478·2019-08-30 12:43
閱讀 2930·2019-08-29 18:39
閱讀 3201·2019-08-29 12:51