
本文翻譯自 Clean Swift
過(guò)去兩年,有許多關(guān)于 VIPER 的文章,這是一種在iOS項(xiàng)目中十分流行的架構(gòu)模式。 如果你對(duì)它還不了解,可以看看 這篇文章。
今天,我想談?wù)劥?VIPER 的另一個(gè)選擇 -- Clean Swift。
首先,Clean Swift 和 VIPER 有些相似;然而,在查看它們模塊間交互的方式后,兩者的區(qū)別也顯而易見。在 VIPER 中,模塊交互的基礎(chǔ)是 Presenter,Presenter 將用戶的請(qǐng)求傳遞到 Interactor 處理,Interactor 處理完成后將結(jié)果返回給 Presenter,Presenter 把結(jié)果格式化為 View Controller 要展示的形式,并返回給 View Controller:

在 Clean Swift 中,主要的模塊是 View Controller,Interactor,Presenter,和 VIPER 有點(diǎn)相似:

但與 VIPER 不同的是,在 Clean Swift 中,不同模塊之間的交互是發(fā)生在一個(gè)圓形循環(huán)中。數(shù)據(jù)的傳遞方式基于協(xié)議(VIPER 也是如此),采用協(xié)議的好處是 允許我們?cè)谛枰臅r(shí)候?qū)⒋讼到y(tǒng)中的一個(gè)組件,替換為另一個(gè)遵守了相同協(xié)議的對(duì)象。它們之間的交互處理過(guò)程通常像這樣:用戶點(diǎn)擊一個(gè)按鈕,View Controller 創(chuàng)建一個(gè)攜帶相關(guān)信息的對(duì)象,把它送給 Interactor。Interactor 則根據(jù)業(yè)務(wù)邏輯啟動(dòng)特定的場(chǎng)景,得到結(jié)果并傳給 Presenter。Presenter 將結(jié)果格式化為需要展示的樣式,再發(fā)給 View Controller 展示。讓我們來(lái)仔細(xì)看看 Clean Swift 的各個(gè)模塊吧。
View (View Controller)
View Controller 負(fù)責(zé)配置所有視圖相關(guān)的屬性(與 VIPER 相似),像顏色、UILabel 的樣式或者布局。因此,在 Clean Swift中的每個(gè) UIViewController 都實(shí)現(xiàn)了一個(gè)特定的輸入?yún)f(xié)議(DisplayLogic)來(lái)展示數(shù)據(jù) 或 展示用戶的操作結(jié)果。
Interactor
Interactor 包含了所有的業(yè)務(wù)邏輯。它接收來(lái)自 View Controller 的用戶的操作事件及參數(shù)(例如:輸入框文本的變化 或 按鈕的點(diǎn)擊)。這些事件被定義在 Interactor 的輸入?yún)f(xié)議中(BusinessLogic)。在處理完成后,Interactor 將結(jié)果傳遞給 Presenter,Presenter 會(huì)格式化結(jié)果并傳遞給 View Controller。
在 Clean Swift 中,Interactor 只會(huì)接收來(lái)自 View Controller 的請(qǐng)求。然而,而在 VIPER 中,這些請(qǐng)求將通過(guò) Presenter 作為中間層來(lái)傳遞。
Presenter
Presenter 會(huì)準(zhǔn)備好展示給用戶的數(shù)據(jù)并輸出到 View Controller。這些輸出的結(jié)果會(huì)遵守 View Controller 的輸入?yún)f(xié)議(DisplayLogic)。例如,Presenter 可以改變文本的格式,將枚舉的顏色轉(zhuǎn)換為 RGB,等等。
Worker
為了避免繁瑣的業(yè)務(wù)細(xì)節(jié)和重復(fù)的邏輯導(dǎo)致 Interactor 過(guò)于復(fù)雜和龐大,你可以添加額外的 Worker 模塊。對(duì)于簡(jiǎn)單的項(xiàng)目來(lái)說(shuō),Worker 并非是必要的選擇,但是在復(fù)雜的場(chǎng)景下,它將為 Interactor 分擔(dān)部分任務(wù)。例如:Worker 可以實(shí)現(xiàn)與數(shù)據(jù)庫(kù)交互的邏輯,特別是當(dāng)程序的不同地方使用相同的查詢時(shí)。
Router
Router 的職責(zé)是在不同界面之間跳轉(zhuǎn)和傳遞數(shù)據(jù)。它引用了 View Controller,這是因?yàn)樵?iOS 系統(tǒng)上界面跳轉(zhuǎn)一直是 View Controller 的職責(zé)。如果你是用 segues,則可以通過(guò)在 PrepareForSegue 方法中調(diào)用 Router 方法來(lái)簡(jiǎn)化界面跳轉(zhuǎn)的初始化,因?yàn)?Router 已經(jīng)知道如何傳輸數(shù)據(jù)并在沒(méi)有 Interactor / Presenter 的情況下完成此操作。使用 Interactor 中實(shí)現(xiàn)的每個(gè)界面的 DataStore 協(xié)議傳遞數(shù)據(jù),該協(xié)議還限制了從路由器訪問(wèn)界面內(nèi)部數(shù)據(jù)的能力。
Models
Models 是純數(shù)據(jù)結(jié)構(gòu),它描述了在不同模塊間數(shù)據(jù)傳遞的信息。業(yè)務(wù)邏輯(BusinessLogic)的每個(gè)實(shí)現(xiàn)函數(shù)都有自己的 Model。Request 用于從 View Controller 向 Interactor 發(fā)送請(qǐng)求。Response 是 Interactor 對(duì) Presenter 的響應(yīng)。ViewModel 描述了從 Presenter 傳輸?shù)?View Controller 進(jìn)行顯示的數(shù)據(jù)。
Example
讓我們用一個(gè)簡(jiǎn)單的 例子 來(lái)研究一下這個(gè)架構(gòu)。這是一個(gè)簡(jiǎn)化表示 ContactBook 的應(yīng)用程序,但它足以讓我們理解 Clean Swift 的基礎(chǔ)。這個(gè) app 包括聯(lián)系人列表,以及添加和編輯聯(lián)系人的功能。
每個(gè) View Controller 包含了一個(gè)實(shí)現(xiàn)了業(yè)務(wù)邏輯協(xié)議(BusinessLogic)的對(duì)象,叫做 Interactor,也包含一個(gè) Router 對(duì)象,它實(shí)現(xiàn)了界面間數(shù)據(jù)傳輸和跳轉(zhuǎn)的協(xié)議。
你可以在 View Controller 中用一個(gè)單獨(dú)的私有方法中配置 Interactor 和 Router;或者,有些開發(fā)者認(rèn)為 View Controller 不需要參與此配置,你也可以創(chuàng)建一個(gè) Configurator 單例來(lái)將這部分的配置代碼抽離出 View Controller,并且 Configurator 不應(yīng)訪問(wèn) View Controller 中的其他部分代碼。Configurator 類并不存在于 Uncle Bob's clean architecture 的描述中,也不出現(xiàn)在經(jīng)典的 VIPER 架構(gòu)中。在添加聯(lián)系人的界面使用 Configurator 如下所示:
Configurator 包含一個(gè)配置方法,與 View Controller 中的配置方法相同。
[譯者注]:Clean Swift 已不使用 Configurator 了,取而代之的是在 View Controller 中使用 setup() 來(lái)配置]
View Controller 實(shí)現(xiàn)中的另一個(gè)重點(diǎn)是 prepareForSegue() 方法中的代碼:
細(xì)心的讀者可能已注意到 Router 被要求遵守 NSObjectProtocol。這樣做是為了在使用 segue 時(shí),我們可以使用此協(xié)議的標(biāo)準(zhǔn)方法進(jìn)行路由。為了支持這種簡(jiǎn)單的重定向,segue 標(biāo)識(shí)符的命名必須與 Router 方法名稱的結(jié)尾完全相同。例如:點(diǎn)擊 cell 跳轉(zhuǎn)查看聯(lián)系人界面,在 Storyboard 中有一個(gè) segue 與之關(guān)聯(lián),它的標(biāo)識(shí)符是 “ViewContact”,那么,在 Router 中就應(yīng)該有一個(gè)相應(yīng)的 “routeToViewContact()” 方法。
向 Interactor 請(qǐng)求數(shù)據(jù)用來(lái)展示看起來(lái)也很容易:
讓我們看一下 Interactor。Interactor 實(shí)現(xiàn)了 ContactListDataStore protocol,這個(gè)協(xié)議描述了存儲(chǔ)和訪問(wèn)的數(shù)據(jù)。在我們的例子中,它只是一個(gè)聯(lián)系人數(shù)組,用 getter 方法限制了它在 Router 中不能被其他 模塊修改。
這是我們聯(lián)系人列表 業(yè)務(wù)邏輯協(xié)議的實(shí)現(xiàn):
它從 ContactListWorker 接收 聯(lián)系人數(shù)據(jù)。在這個(gè)例子中,Worker 負(fù)責(zé)數(shù)據(jù)的加載方式。Worker 可以訪問(wèn)第三方服務(wù),例如,決定是從緩存中獲取數(shù)據(jù)還是從網(wǎng)絡(luò)下載。Interactor 收到數(shù)據(jù)之后,發(fā)送一個(gè) response 給 Presenter 來(lái)準(zhǔn)備要展示的數(shù)據(jù)。Interactor 包含對(duì) Presenter 的引用,用于實(shí)現(xiàn)這個(gè)功能。
Presenter 只實(shí)現(xiàn)了一個(gè)協(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ù),因?yàn)樗皇且粋€(gè)典型的聯(lián)系人列表。但是,如果聯(lián)系人列表界面包含過(guò)濾等限制,則可以將過(guò)濾參數(shù)添加到此查詢請(qǐng)求中。Interactor 的 response model 包含了必要的聯(lián)系人數(shù)組,ViewModel 也由用于展示的 DisplayedContact 數(shù)組組成。
為什么要使用 Clean Swift 呢?
讓我們思考一下這個(gè)架構(gòu)的優(yōu)缺點(diǎn)。
首先,Clean Swift 有模板代碼使得我們很容易就可以創(chuàng)建一個(gè)模塊。這些模板代碼可以針對(duì)各種體系結(jié)構(gòu)編寫,但是當(dāng)它們開箱即用時(shí),可以為你節(jié)省幾個(gè)小時(shí)的時(shí)間。
第二,這個(gè)架構(gòu),包括 VIPER,都是容易測(cè)試的。任何模塊都可以通過(guò) mock 輕松替換掉,因?yàn)槊總€(gè)模塊的功能都在協(xié)議中描述。當(dāng)我們同時(shí)實(shí)現(xiàn)業(yè)務(wù)邏輯和相關(guān)測(cè)試時(shí),我們已經(jīng)在使用測(cè)試驅(qū)動(dòng)開發(fā)(TDD)。由于每個(gè)邏輯案例的都是由協(xié)議定義的,因此可以先編寫一個(gè)確定其行為的測(cè)試,然后直接實(shí)現(xiàn)該方法。
第三,Clean Swift 有單向的數(shù)據(jù)處理和決策流程(這是和 VIPER 相對(duì)的),始終只有一個(gè)循環(huán) View Controller --> Interactor --> Presenter --> View Controller,這簡(jiǎn)化了重構(gòu)。因?yàn)榇蟛糠值臅r(shí)候,將會(huì)修改更少的實(shí)體。因此,使用 Clean Swift 架構(gòu)時(shí),具有經(jīng)常更改邏輯的項(xiàng)目更容易重構(gòu)。在 Clean Swift 中,可以通過(guò)兩種方式分離實(shí)體:
- 通過(guò)在聲明輸入和輸出協(xié)議中隔離組件

- 通過(guò)使用結(jié)構(gòu)隔離功能,并將數(shù)據(jù)封裝到單獨(dú)的 Request/Response/ViewModel 中。每個(gè)功能都有其邏輯,并在同一過(guò)程中進(jìn)行控制,不會(huì)與其他功能重疊。
Clean Swift 不應(yīng)該用于沒(méi)有長(zhǎng)遠(yuǎn)眼光的小型項(xiàng)目,也不應(yīng)該用于原型。相反,長(zhǎng)期的項(xiàng)目和具有大量業(yè)務(wù)邏輯的項(xiàng)目非常適合這種架構(gòu)。當(dāng)項(xiàng)目為 Mac OS 和 iOS 兩個(gè)平臺(tái)開發(fā)(或者有此計(jì)劃)時(shí),使用 Clean Swift 非常方便,因?yàn)榇蟛糠执a模塊(除 View Controller 外,有時(shí)也會(huì)包括 Router 在內(nèi))可以不用修改就被重用。
不當(dāng)之處,還請(qǐng)指正