Clean Swift

Clean Swift.png

本文翻譯自 Clean Swift

GitHub Demo

過(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:

VIPER VIP.png

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

Clean Swift VIP.png

但與 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)配置]

詳見:Zero configuration

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é)議中隔離組件
Clean Swift Isolation.png
  • 通過(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)指正

附:Clean Swift 官網(wǎng)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容