本文翻譯自 Clean Swift
過去兩年,有許多關(guān)于 VIPER 的文章,這是一種在iOS項目中十分流行的架構(gòu)模式捐寥。 如果你對它還不了解,可以看看 這篇文章祖驱。
今天握恳,我想談?wù)劥?VIPER 的另一個選擇 -- Clean Swift。
首先捺僻,Clean Swift 和 VIPER 有些相似睡互;然而,在查看它們模塊間交互的方式后陵像,兩者的區(qū)別也顯而易見就珠。在 VIPER 中,模塊交互的基礎(chǔ)是 Presenter醒颖,Presenter 將用戶的請求傳遞到 Interactor 處理妻怎,Interactor 處理完成后將結(jié)果返回給 Presenter,Presenter 把結(jié)果格式化為 View Controller 要展示的形式泞歉,并返回給 View Controller:
在 Clean Swift 中逼侦,主要的模塊是 View Controller匿辩,Interactor,Presenter榛丢,和 VIPER 有點(diǎn)相似:
但與 VIPER 不同的是铲球,在 Clean Swift 中,不同模塊之間的交互是發(fā)生在一個圓形循環(huán)中晰赞。數(shù)據(jù)的傳遞方式基于協(xié)議(VIPER 也是如此)稼病,采用協(xié)議的好處是 允許我們在需要的時候?qū)⒋讼到y(tǒng)中的一個組件,替換為另一個遵守了相同協(xié)議的對象掖鱼。它們之間的交互處理過程通常像這樣:用戶點(diǎn)擊一個按鈕然走,View Controller 創(chuàng)建一個攜帶相關(guān)信息的對象,把它送給 Interactor戏挡。Interactor 則根據(jù)業(yè)務(wù)邏輯啟動特定的場景芍瑞,得到結(jié)果并傳給 Presenter。Presenter 將結(jié)果格式化為需要展示的樣式褐墅,再發(fā)給 View Controller 展示拆檬。讓我們來仔細(xì)看看 Clean Swift 的各個模塊吧。
View (View Controller)
View Controller 負(fù)責(zé)配置所有視圖相關(guān)的屬性(與 VIPER 相似)妥凳,像顏色竟贯、UILabel 的樣式或者布局。因此猾封,在 Clean Swift中的每個 UIViewController 都實(shí)現(xiàn)了一個特定的輸入?yún)f(xié)議(DisplayLogic)來展示數(shù)據(jù) 或 展示用戶的操作結(jié)果澄耍。
Interactor
Interactor 包含了所有的業(yè)務(wù)邏輯。它接收來自 View Controller 的用戶的操作事件及參數(shù)(例如:輸入框文本的變化 或 按鈕的點(diǎn)擊)晌缘。這些事件被定義在 Interactor 的輸入?yún)f(xié)議中(BusinessLogic)齐莲。在處理完成后,Interactor 將結(jié)果傳遞給 Presenter磷箕,Presenter 會格式化結(jié)果并傳遞給 View Controller选酗。
在 Clean Swift 中,Interactor 只會接收來自 View Controller 的請求岳枷。然而芒填,而在 VIPER 中,這些請求將通過 Presenter 作為中間層來傳遞空繁。
Presenter
Presenter 會準(zhǔn)備好展示給用戶的數(shù)據(jù)并輸出到 View Controller殿衰。這些輸出的結(jié)果會遵守 View Controller 的輸入?yún)f(xié)議(DisplayLogic)。例如盛泡,Presenter 可以改變文本的格式闷祥,將枚舉的顏色轉(zhuǎn)換為 RGB,等等傲诵。
Worker
為了避免繁瑣的業(yè)務(wù)細(xì)節(jié)和重復(fù)的邏輯導(dǎo)致 Interactor 過于復(fù)雜和龐大凯砍,你可以添加額外的 Worker 模塊箱硕。對于簡單的項目來說,Worker 并非是必要的選擇悟衩,但是在復(fù)雜的場景下剧罩,它將為 Interactor 分擔(dān)部分任務(wù)。例如:Worker 可以實(shí)現(xiàn)與數(shù)據(jù)庫交互的邏輯座泳,特別是當(dāng)程序的不同地方使用相同的查詢時惠昔。
Router
Router 的職責(zé)是在不同界面之間跳轉(zhuǎn)和傳遞數(shù)據(jù)。它引用了 View Controller钳榨,這是因為在 iOS 系統(tǒng)上界面跳轉(zhuǎn)一直是 View Controller 的職責(zé)舰罚。如果你是用 segues纽门,則可以通過在 PrepareForSegue 方法中調(diào)用 Router 方法來簡化界面跳轉(zhuǎn)的初始化薛耻,因為 Router 已經(jīng)知道如何傳輸數(shù)據(jù)并在沒有 Interactor / Presenter 的情況下完成此操作。使用 Interactor 中實(shí)現(xiàn)的每個界面的 DataStore 協(xié)議傳遞數(shù)據(jù)赏陵,該協(xié)議還限制了從路由器訪問界面內(nèi)部數(shù)據(jù)的能力饼齿。
Models
Models 是純數(shù)據(jù)結(jié)構(gòu),它描述了在不同模塊間數(shù)據(jù)傳遞的信息蝙搔。業(yè)務(wù)邏輯(BusinessLogic)的每個實(shí)現(xiàn)函數(shù)都有自己的 Model缕溉。Request 用于從 View Controller 向 Interactor 發(fā)送請求。Response 是 Interactor 對 Presenter 的響應(yīng)吃型。ViewModel 描述了從 Presenter 傳輸?shù)?View Controller 進(jìn)行顯示的數(shù)據(jù)证鸥。
Example
讓我們用一個簡單的 例子 來研究一下這個架構(gòu)。這是一個簡化表示 ContactBook 的應(yīng)用程序勤晚,但它足以讓我們理解 Clean Swift 的基礎(chǔ)枉层。這個 app 包括聯(lián)系人列表,以及添加和編輯聯(lián)系人的功能赐写。
每個 View Controller 包含了一個實(shí)現(xiàn)了業(yè)務(wù)邏輯協(xié)議(BusinessLogic)的對象鸟蜡,叫做 Interactor,也包含一個 Router 對象挺邀,它實(shí)現(xiàn)了界面間數(shù)據(jù)傳輸和跳轉(zhuǎn)的協(xié)議揉忘。
你可以在 View Controller 中用一個單獨(dú)的私有方法中配置 Interactor 和 Router;或者端铛,有些開發(fā)者認(rèn)為 View Controller 不需要參與此配置泣矛,你也可以創(chuàng)建一個 Configurator 單例來將這部分的配置代碼抽離出 View Controller,并且 Configurator 不應(yīng)訪問 View Controller 中的其他部分代碼禾蚕。Configurator 類并不存在于 Uncle Bob's clean architecture 的描述中您朽,也不出現(xiàn)在經(jīng)典的 VIPER 架構(gòu)中。在添加聯(lián)系人的界面使用 Configurator 如下所示:
Configurator 包含一個配置方法夕膀,與 View Controller 中的配置方法相同虚倒。
[譯者注]:Clean Swift 已不使用 Configurator 了美侦,取而代之的是在 View Controller 中使用 setup() 來配置]
View Controller 實(shí)現(xiàn)中的另一個重點(diǎn)是 prepareForSegue() 方法中的代碼:
細(xì)心的讀者可能已注意到 Router 被要求遵守 NSObjectProtocol。這樣做是為了在使用 segue 時魂奥,我們可以使用此協(xié)議的標(biāo)準(zhǔn)方法進(jìn)行路由菠剩。為了支持這種簡單的重定向,segue 標(biāo)識符的命名必須與 Router 方法名稱的結(jié)尾完全相同耻煤。例如:點(diǎn)擊 cell 跳轉(zhuǎn)查看聯(lián)系人界面具壮,在 Storyboard 中有一個 segue 與之關(guān)聯(lián),它的標(biāo)識符是 “ViewContact”哈蝇,那么棺妓,在 Router 中就應(yīng)該有一個相應(yīng)的 “routeToViewContact()” 方法。
向 Interactor 請求數(shù)據(jù)用來展示看起來也很容易:
讓我們看一下 Interactor炮赦。Interactor 實(shí)現(xiàn)了 ContactListDataStore protocol怜跑,這個協(xié)議描述了存儲和訪問的數(shù)據(jù)。在我們的例子中吠勘,它只是一個聯(lián)系人數(shù)組性芬,用 getter 方法限制了它在 Router 中不能被其他 模塊修改。
這是我們聯(lián)系人列表 業(yè)務(wù)邏輯協(xié)議的實(shí)現(xiàn):
它從 ContactListWorker 接收 聯(lián)系人數(shù)據(jù)剧防。在這個例子中植锉,Worker 負(fù)責(zé)數(shù)據(jù)的加載方式。Worker 可以訪問第三方服務(wù)峭拘,例如俊庇,決定是從緩存中獲取數(shù)據(jù)還是從網(wǎng)絡(luò)下載。Interactor 收到數(shù)據(jù)之后鸡挠,發(fā)送一個 response 給 Presenter 來準(zhǔn)備要展示的數(shù)據(jù)辉饱。Interactor 包含對 Presenter 的引用,用于實(shí)現(xiàn)這個功能宵凌。
Presenter 只實(shí)現(xiàn)了一個協(xié)議 -- ContactListPresentationLogic鞋囊,在我們的例子中,它將聯(lián)系人的姓名和姓氏的首字母改為大寫瞎惫,然后形成 DisplayedContact 的數(shù)據(jù)模型溜腐,并將其傳遞給 View Controller 以顯示。
至此瓜喇,VIP 循環(huán)就完成了挺益,View Controller 展示數(shù)據(jù),實(shí)現(xiàn)協(xié)議 ContactListDisplayLogic 的方法。
以下是顯示聯(lián)系人的數(shù)據(jù)模型:
在這種情況下,查詢不包含查詢參數(shù)贫奠,因為它只是一個典型的聯(lián)系人列表谋竖。但是,如果聯(lián)系人列表界面包含過濾等限制参滴,則可以將過濾參數(shù)添加到此查詢請求中瘸羡。Interactor 的 response model 包含了必要的聯(lián)系人數(shù)組代赁,ViewModel 也由用于展示的 DisplayedContact 數(shù)組組成甘耿。
為什么要使用 Clean Swift 呢踊兜?
讓我們思考一下這個架構(gòu)的優(yōu)缺點(diǎn)。
首先佳恬,Clean Swift 有模板代碼使得我們很容易就可以創(chuàng)建一個模塊捏境。這些模板代碼可以針對各種體系結(jié)構(gòu)編寫,但是當(dāng)它們開箱即用時毁葱,可以為你節(jié)省幾個小時的時間垫言。
第二,這個架構(gòu)倾剿,包括 VIPER筷频,都是容易測試的。任何模塊都可以通過 mock 輕松替換掉柱告,因為每個模塊的功能都在協(xié)議中描述截驮。當(dāng)我們同時實(shí)現(xiàn)業(yè)務(wù)邏輯和相關(guān)測試時笑陈,我們已經(jīng)在使用測試驅(qū)動開發(fā)(TDD)际度。由于每個邏輯案例的都是由協(xié)議定義的,因此可以先編寫一個確定其行為的測試涵妥,然后直接實(shí)現(xiàn)該方法乖菱。
第三,Clean Swift 有單向的數(shù)據(jù)處理和決策流程(這是和 VIPER 相對的)蓬网,始終只有一個循環(huán) View Controller --> Interactor --> Presenter --> View Controller窒所,這簡化了重構(gòu)。因為大部分的時候帆锋,將會修改更少的實(shí)體吵取。因此,使用 Clean Swift 架構(gòu)時锯厢,具有經(jīng)常更改邏輯的項目更容易重構(gòu)皮官。在 Clean Swift 中,可以通過兩種方式分離實(shí)體:
- 通過在聲明輸入和輸出協(xié)議中隔離組件
- 通過使用結(jié)構(gòu)隔離功能实辑,并將數(shù)據(jù)封裝到單獨(dú)的 Request/Response/ViewModel 中捺氢。每個功能都有其邏輯,并在同一過程中進(jìn)行控制剪撬,不會與其他功能重疊摄乒。
Clean Swift 不應(yīng)該用于沒有長遠(yuǎn)眼光的小型項目,也不應(yīng)該用于原型。相反馍佑,長期的項目和具有大量業(yè)務(wù)邏輯的項目非常適合這種架構(gòu)斋否。當(dāng)項目為 Mac OS 和 iOS 兩個平臺開發(fā)(或者有此計劃)時,使用 Clean Swift 非常方便拭荤,因為大部分代碼模塊(除 View Controller 外如叼,有時也會包括 Router 在內(nèi))可以不用修改就被重用。
不當(dāng)之處穷劈,還請指正