轉(zhuǎn)自:掘金 iOS一葉
iOS APP 架構(gòu)設(shè)計(jì)
一,APP架構(gòu)概述
應(yīng)用架構(gòu)
2.Model 和 View
3 ^[1]^ . App 的本質(zhì)是反饋回路
4.架構(gòu)技術(shù)
5.App^[2]^ 任務(wù)
6.iOS 架構(gòu)的5中模式:
二,APP設(shè)計(jì)常用的5種模式概覽
Model-View-Controller
Model-View-ViewModel+協(xié)調(diào)器
Model-View-Controller+ViewState
Model 適配器-View 綁定器 (MAVB)
Elm 架構(gòu) (TEA)
三,其他APP架構(gòu)模式
Model-View-Presenter
VIPER,Riblets,和其他 “Clean” 架構(gòu)
一,APP架構(gòu)概述
1. 應(yīng)用架構(gòu)
App 架構(gòu)是軟件設(shè)計(jì)的一個(gè)分支,它關(guān)心如何設(shè)計(jì)一個(gè) app 的結(jié)構(gòu)。具體來(lái)說(shuō),它關(guān)注于兩個(gè) 方面:如何將 app 分解為不同的接口和概念層次部件,以及這些部件之間和自身的不同操作中 所使用的控制流和數(shù)據(jù)流路徑。
我們通常使用簡(jiǎn)單的框圖來(lái)解釋 app 的架構(gòu)。比如,Apple 的 MVC 模式可以通過(guò) model、 view 和 controller 三層結(jié)構(gòu)來(lái)描述。
上面框圖中的模塊展示了這個(gè)模式中不同名字的三個(gè)層次。在一個(gè) MVC 項(xiàng)目中,絕大部分的代 碼都會(huì)落到其中某個(gè)層上。箭頭表示了這些層進(jìn)行連接的方式。
但是,這種簡(jiǎn)單的框圖幾乎無(wú)法解釋在實(shí)踐中模式的操作方式。這是因?yàn)樵趯?shí)際的 app 架構(gòu)中, 部件的構(gòu)建有非常多的可能性。事件流在層中穿梭的方式是什么?部件之間是否應(yīng)該在編譯期 間或者運(yùn)行時(shí)持有對(duì)方?要怎么讀取和修改不同部件中的數(shù)據(jù)?以及狀態(tài)的變更應(yīng)該以哪條路 徑在 app 中穿行?
2.Model 和 View
在最高的層級(jí)上,app 架構(gòu)其實(shí)就是一套分類(lèi),app 中不同的部件會(huì)被歸納到某個(gè)類(lèi)型中去。在本書(shū)中,我們將這些不同的種類(lèi)叫做層次:一個(gè)層次指的是,遵循一些基本規(guī)則并負(fù)責(zé)特定 功能的接口和其他代碼的集合。
Model 層和 View 層是這些分類(lèi)中最為常?的兩個(gè)。
Model 層是 app 的內(nèi)容,它不依賴于 (像 UIKit 那樣的) 任何 app 框架。也就是說(shuō),程序員對(duì) model 層有完全的控制。Model 層通常包括 model 對(duì)象 (在錄音 app 中的例子是文件夾和錄音對(duì)象) 和協(xié)調(diào)對(duì)象 (比如我們的 app 例子中的負(fù)責(zé)在磁盤(pán)上存儲(chǔ)數(shù)據(jù)的 Store 類(lèi)型)。被存儲(chǔ)在 磁盤(pán)上的那部分 model 我們稱(chēng)之為文檔 model (documentation model)。
View 層是依賴于 app 框架的部分,它使 model 層可?,并允許用戶進(jìn)行交互,從而將 model 層轉(zhuǎn)變?yōu)橐粋€(gè) app。當(dāng)創(chuàng)建 iOS 應(yīng)用時(shí),view 層幾乎總是直接使用 UIKit。不過(guò),我們也會(huì)看 到在有些架構(gòu)中,會(huì)使用 UIKit 的封裝來(lái)實(shí)現(xiàn)不同的 view 層。另外,對(duì)一些其他的像是游戲這 樣的自定義應(yīng)用,view 層可以不是 UIKit 或者 AppKit,它可能是 SceneKit 或者 OpenGL 的某 種封裝。
有時(shí)候,我們選擇使用結(jié)構(gòu)體或者枚舉來(lái)表示 model 或者 view 的實(shí)例,而不使用類(lèi)的對(duì)象。在實(shí)踐中,類(lèi)型之間的區(qū)別非常重要,但是當(dāng)我們?cè)?model 層中談到對(duì)象、結(jié)構(gòu)體和枚舉時(shí), 我們會(huì)將三者統(tǒng)一地稱(chēng)為 model 對(duì)象。類(lèi)似地,我們也會(huì)把 view 層的實(shí)例叫做 view 對(duì)象,實(shí) 際上它們也可能是對(duì)象、結(jié)構(gòu)體或者枚舉。
View 對(duì)象通常會(huì)構(gòu)成一個(gè)單一的 view 層級(jí),在這個(gè)層級(jí)中,所有的 view 對(duì)象通過(guò)樹(shù)結(jié)構(gòu)的方 式連接起來(lái)。在樹(shù)的根部是整個(gè)屏幕,屏幕中存在若干窗口,接下來(lái)在樹(shù)的分支和葉子上是更 多的小 view。類(lèi)似地,view controller 也通常會(huì)形成 view controller 層級(jí)。不過(guò),model 對(duì) 象卻不需要有明確的層級(jí)關(guān)系,在程序中它們可以是互不關(guān)聯(lián)的獨(dú)立 model。
當(dāng)我們提到 view 時(shí),通常指的是像一個(gè)按鈕或者一個(gè)文本 label 這樣的單一 view 對(duì)象。當(dāng)我 們提到 model 時(shí),我們通常指的也是像一個(gè) Recording 實(shí)例或者 Folder 實(shí)例這樣的單個(gè) model 對(duì)象。在該話題的大多數(shù)文獻(xiàn)中,“model” 在不同上下文中指的可能是不同的事情。它 可以指代一個(gè) model 層,model 層中的具體的若干對(duì)象,文檔 model,或者是 model 層中不 關(guān)聯(lián)的文檔。雖然可能會(huì)顯得啰嗦,我們還是會(huì)嘗試在本書(shū)中盡量明確地區(qū)分這些不同含義。
為什么 Model 和 View 的分類(lèi)會(huì)被認(rèn)為是基礎(chǔ)中的基礎(chǔ)
當(dāng)然啦,就算不區(qū)分 model 層和 view 層,寫(xiě)出一個(gè) app 也是絕對(duì)可能的。比如說(shuō),在一個(gè)簡(jiǎn) 單的對(duì)話框中,通常就沒(méi)有獨(dú)立的 model 數(shù)據(jù)。在用戶點(diǎn)擊 OK 按鈕的時(shí)候,我們可以直接從 用戶界面元素中讀取狀態(tài)。不過(guò)通常情況下,model 層的缺失,會(huì)讓程序的行為缺乏對(duì)于清晰 規(guī)則的依據(jù),這會(huì)使得代碼難以維護(hù)。
定義一個(gè) model 層的最重要的理由是,它為我們的程序提供一個(gè)表述事實(shí)的單一來(lái)源,這會(huì)讓 邏輯清晰,行為正確。這樣一來(lái),我們的程序便不會(huì)被應(yīng)用框架中的實(shí)現(xiàn)細(xì)節(jié)所支配。
應(yīng)用框架為我們提供了構(gòu)建 app 所需要的基礎(chǔ)設(shè)施。在本書(shū)中,我們使用 Cocoa - 或者更精確 說(shuō),根據(jù)目標(biāo)平臺(tái),使用 UIKit,AppKit 或者 WatchKit - 來(lái)作為應(yīng)用框架。
如果 model 層能做到和應(yīng)用框架分離,我們就可以完全在 app 的范圍之外使用它。我們可以很 容易地在另外的測(cè)試套件中運(yùn)行它,或者用一個(gè)完全不同的應(yīng)用框架重寫(xiě)新的 view 層。這個(gè) model 層將能夠用于 Android,macOS 或者 Windows 版本的 app 中。
3.App 的本質(zhì)是反饋回路
View 層和 model 層需要交流。所以,兩者之間需要存在連接。假設(shè) view 層和 model 層是被清
晰地分開(kāi),而且不存在無(wú)法解耦的聯(lián)結(jié)的話,兩者之間的通訊就需要一些形式的翻譯:
從根本上說(shuō),用戶界面是一個(gè)同時(shí)負(fù)責(zé)展示和輸入功能的反饋設(shè)備,所以毫無(wú)疑問(wèn),這導(dǎo)致的 結(jié)果就是一個(gè)反饋回路。每個(gè) app 設(shè)計(jì)模式所面臨的挑戰(zhàn)是如何處理這張圖表中箭頭所包含的 交流,依賴和變換。
在 model 層和 view 層之間不同的路徑擁有不同的名字。用戶發(fā)起的事件會(huì)導(dǎo)致 view 的響應(yīng), 我們把由此引起的代碼路徑稱(chēng)為 view action,像是點(diǎn)擊按鈕或者選中 table view 中的某一行 就屬于 view action。當(dāng)一個(gè) view action 被送到 model 層時(shí),它會(huì)被轉(zhuǎn)變?yōu)?model action (或 者說(shuō),讓 model 對(duì)象執(zhí)行一個(gè) action 或者進(jìn)行更新的命令)。
這種命令也被叫做一個(gè)消息 (特別 在當(dāng) model 是被 reducer 改變時(shí),我們會(huì)這么稱(chēng)呼它)。將 view action 轉(zhuǎn)變?yōu)?model action 的操作,以及路徑上的其他邏輯被叫做交互邏輯。
一個(gè)或者多個(gè) model 對(duì)象上狀態(tài)的改變叫做 model 變更。Model 的變更通常會(huì)觸發(fā)一個(gè)model 通知,比如說(shuō)從 model 層發(fā)出一個(gè)可觀測(cè)的通知,它描述 model 層中什么內(nèi)容發(fā)生了 改變。當(dāng) view 依賴于 model 數(shù)據(jù)時(shí),通知會(huì)觸發(fā)一個(gè) view 變更,來(lái)更改 view 層中的內(nèi)容。
這些通知可以以多種形式存在:Foundation 中的 Noti?cation,代理,回調(diào),或者是其他機(jī)制, 都是可以的。將 model 通知和數(shù)據(jù)轉(zhuǎn)變?yōu)?view 更改的操作,以及路徑上的其他邏輯被叫做表 現(xiàn)邏輯。
根據(jù) app 模式的不同,有些狀態(tài)可能是在文檔 model 之外進(jìn)行維護(hù)的,這樣一來(lái),更新這些狀 態(tài)的行為就不會(huì)追隨文檔 model 的路徑。在很多模式中的導(dǎo)航狀態(tài)就這種行為的一個(gè)常?例 子,在 view 層級(jí)中的某個(gè)部分 (或者按照 Cocoa Storyboard 中使用的術(shù)語(yǔ),將它稱(chēng)為 scene) 可能會(huì)被換出或者換入層級(jí)中。
在 app 中非文檔 model 的狀態(tài)被叫做 view state。在 Cocoa 里,大部分 view 對(duì)象都管理著它 們自己的 view state,controller 對(duì)象則管理剩余的 view state。在 Cocoa view state 的框圖 中,通常會(huì)有加在反饋回路上的捷徑,或者單個(gè)層自身進(jìn)行循環(huán)。在有一些架構(gòu)中,view state 不屬于 controller 層,而是屬于 model 層的部分 (不過(guò),根據(jù)定義,view controller 并不是文 檔 model 的一部分)。
當(dāng)所有的狀態(tài)都在 model 層中被維護(hù),而且所有的變更都通過(guò)完整的反饋回路路徑進(jìn)行傳遞 時(shí),我們就將它稱(chēng)為單向數(shù)據(jù)流。當(dāng)任意的 view 對(duì)象或者中間層對(duì)象只能夠通過(guò) model 發(fā)出 的通知來(lái)進(jìn)行創(chuàng)建和更新 (換句話說(shuō),view 或者中間層不能通過(guò)捷徑來(lái)更新自身或者其他的 view) 時(shí),這個(gè)模式通常就是單向的。
4.架構(gòu)技術(shù)
Apple 平臺(tái)的標(biāo)準(zhǔn) Cocoa 框架提供了一些架構(gòu)工具。Noti?cation 將值從單一源廣播給若干個(gè) 收聽(tīng)者。鍵值觀察 (KVO) 可以將某個(gè)對(duì)象上屬性的改變報(bào)告給另一個(gè)對(duì)象。然而,Cocoa 中的 架構(gòu)工具十分有限,我們將會(huì)使用到一些額外的框架。
本書(shū)中使用到的第三方技術(shù)中包含了響應(yīng)式編程。響應(yīng)式編程也是一種用來(lái)交流變更的工具, 不過(guò)和通知或者 KVO 不同的是,它專(zhuān)注于在源和目標(biāo)之間進(jìn)行變形,讓邏輯可以在部件之間傳 輸信息的同時(shí)得以表達(dá)。
我們可以使用像是響應(yīng)式編程或者 KVO 這樣的技術(shù)創(chuàng)建屬性綁定。綁定接受一個(gè)源和一個(gè)目 標(biāo),無(wú)論何時(shí),只要源發(fā)生了變化,目標(biāo)也將被更新。這和手動(dòng)進(jìn)行觀察在語(yǔ)法上有著不同, 我們不再需要寫(xiě)觀察的邏輯,而只需要指定源和目標(biāo),接下來(lái)框架將會(huì)為我們處理其余部分的 工作。
macOS 上的 Cocoa 包含有 Cocoa 綁定技術(shù),它是一種雙向綁定,所有的可觀察對(duì)象同時(shí)也是 觀察者,在一個(gè)方向上建立綁定連接,會(huì)在反方向也創(chuàng)建一個(gè)連接。不論是 (在MVVM-C 的章
節(jié)中用到的) RxCocoa,還是 (MAVB 章節(jié) 中用到的) CwlViews,都不是雙向綁定的。所以,在
本書(shū)中,所有關(guān)于綁定的討論都只涉及到單向綁定。
5.App任務(wù)
要讓程序正常工作,view 必須依賴于 model 數(shù)據(jù)來(lái)生成和存在,我們配置 view,讓它可以對(duì)
model 進(jìn)行更改,并且能在 model 更新時(shí)也得到更新。所以,我們需要決定在 app 中如何執(zhí)行下列任務(wù):
1.構(gòu)建—誰(shuí)負(fù)責(zé)構(gòu)建model和view,以及將兩者連接起來(lái)?
2.更新model—如何處理viewaction?
3.改變view—如何將model的數(shù)據(jù)應(yīng)用到view上去?
4.viewstate—如何處理導(dǎo)航和其他一些modelstate以外的狀態(tài)?
5.測(cè)試—為了達(dá)到一定程度的測(cè)試覆蓋,要采取怎樣的測(cè)試策略?
6.對(duì)于上面五個(gè)問(wèn)題的回答,是構(gòu)成 app 設(shè)計(jì)模式的基礎(chǔ)要件。在本書(shū)中,我們會(huì)逐一研究這些 設(shè)計(jì)模式。
6.IOS 架構(gòu)的5中模式:
IOS 架構(gòu)的5中模式:
標(biāo)準(zhǔn)的CocoaModel-View-Controller(MVC)是Apple在示例項(xiàng)目中所采用的設(shè)計(jì)模 式。它是 Cocoa app 中最為常?的架構(gòu),同時(shí)也是在 Cocoa 中討論架構(gòu)時(shí)所采用的基 準(zhǔn)線。
Model-View-ViewModel+協(xié)調(diào)器(MVVM-C)是MVC的變種,它擁有單獨(dú)的 “view-model” (視圖模型) 和一個(gè)用來(lái)管理 view controller 的協(xié)調(diào)器。MVVM 使用數(shù)據(jù) 綁定 (通常會(huì)和響應(yīng)式編程一起使用) 來(lái)建立 view-model 層和 view 層之間的連接。
Model-View-Controller+ViewState(MVC+VS)這種模式將所有的viewstate集中到 一個(gè)地方,而不是讓它們散落在 view 和 view controller 中。這和 model 層所遵循的規(guī) 則相同。
Model適配器-View綁定器(ModelAdapter-ViewBinder,MAVB)是本書(shū)的一位作者所 使用的實(shí)驗(yàn)性質(zhì)的架構(gòu)。MAVB 專(zhuān)注于構(gòu)建聲明式的 view,并且拋棄 controller,采用 綁定的方式來(lái)在 model 和 view 之間進(jìn)行通訊。
Elm架構(gòu)(TEA)與MVC或者M(jìn)VVM這樣的常?架構(gòu)完全背道而馳。它使用虛擬view 層級(jí)來(lái)構(gòu)建 view,并使用 reducer 來(lái)在 model 和 view 之間進(jìn)行交互。
二,APP設(shè)計(jì)常用的5種模式概覽
1. Model-View-Controller
在 Cocoa MVC 中,一小部分 controller 對(duì)象負(fù)責(zé)處理 model 或者 view 層范疇之外的所有任 務(wù)。
這意味著,controller 層接收所有的 view action,處理所有的交互邏輯,發(fā)送所有的 model action,接收所有的 model 通知,對(duì)所有用來(lái)展示的數(shù)據(jù)進(jìn)行準(zhǔn)備,最后再將它們應(yīng)用到 view 的變更上去。如果我們?nèi)タ匆幌陆榻B一章中的 app 反饋回路的圖,會(huì)發(fā)現(xiàn)在 model 和 view 之
間的箭頭上,幾乎每個(gè)標(biāo)簽都是 controller。而且要知道,在這幅圖中,構(gòu)建和導(dǎo)航任務(wù)并沒(méi)有 標(biāo)注出來(lái),它們也會(huì)由 controller 來(lái)處理。
下面是 MVC 模式的框圖,它展示了一個(gè) MVC app 的主要通訊路徑:
圖中的虛線部分代表運(yùn)行時(shí)的引用,view 層和 model 層都不會(huì)直接在代碼中引用 controller。實(shí)線部分代表編譯期間的引用,controller 實(shí)例知道自己所連接的 view 和 model 對(duì)象的接口。
如果我們?cè)谶@個(gè)圖標(biāo)外部描上邊界的話,就得到了一個(gè) MVC 版本的 app 反饋回路。注意在圖 表中其他的路徑并不參與整個(gè)反饋回路的路徑 (也就是 view 層和 controller 層上那些指向自身 的箭頭)。
1.構(gòu)建
App 對(duì)象負(fù)責(zé)創(chuàng)建最頂層的 view controller,這個(gè) view controller 將加載 view,并且知道應(yīng) 該從 model 中獲取哪些數(shù)據(jù),然后把它們顯示出來(lái)。Controller 要么顯式地創(chuàng)建和持有 model 層,要么通過(guò)一個(gè)延遲創(chuàng)建的 model 單例來(lái)獲取 model。在多文檔配置中,model 層由更低層 的像是 UIDocument 或 NSDocument 所擁有。那些和 view 相關(guān)的單個(gè) model 對(duì)象,通常會(huì) 被 controller 所引用并緩存下來(lái)。
2.更改 Model
在 MVC 中,controller 主要通過(guò) target/action 機(jī)制和 (由 storyboard 或者代碼進(jìn)行設(shè)置的) delegate 來(lái)接收 view 事件。Controller 知道自己所連接的 view,但是 view 在編譯期間卻沒(méi)有 關(guān)于 controller 接口的信息。當(dāng)一個(gè) view 事件到達(dá)時(shí),controller 有能力改變自身的內(nèi)部狀態(tài), 更改 model,或者直接改變 view 層級(jí)。
3.更改 View
在我們所理解的 MVC 中,當(dāng)一個(gè)更改 model 的 view action 發(fā)生時(shí),controller 不應(yīng)該直接去 操作 view 層級(jí)。正確的做法是,controller 去訂閱 model 通知,并且在當(dāng)通知到達(dá)時(shí)再更改 view 層級(jí)。這樣一來(lái),數(shù)據(jù)流就可以單向進(jìn)行:view action 被轉(zhuǎn)變?yōu)?model 變更,然后 model 發(fā)送通知,這個(gè)通知最后被轉(zhuǎn)為 view 變更。
4.View State
View state 可以按需要被 store 在 view 或者 controller 的屬性中。相對(duì)于影響 model 的 view action,那些只影響 view 或 controller 狀態(tài)的 action 則不需要通過(guò) model 進(jìn)行傳遞。對(duì)于 view state 的存儲(chǔ),可以結(jié)合使用 storyboard 和 UIStateRestoring 來(lái)進(jìn)行實(shí)現(xiàn),storyboard 負(fù)責(zé)記錄活躍的 controller 層級(jí),而 UIStateRestoring 負(fù)責(zé)從 controller 和 view 中讀取數(shù)據(jù)。
5.測(cè)試
在 MVC 中,view controller 與 app 的其他部件緊密相連。邊界的缺失使得為 controller 編寫(xiě) 單元測(cè)試和接口測(cè)試十分困難,集成測(cè)試是余下的為數(shù)不多的可行測(cè)試手段之一。在集成測(cè)試 中,我們構(gòu)建相連接的 view、model 和 controller 層,然后操作 model 或者 view,來(lái)測(cè)試是 否能得到我們想要的結(jié)果。
集成測(cè)試的書(shū)寫(xiě)非常復(fù)雜,而且它涵蓋的范圍太廣了。它不僅僅測(cè)試邏輯,也測(cè)試部件是如何 連接的 (雖然在一些情況下和 UI 測(cè)試的?度并不相同)。不過(guò),在 MVC 中通過(guò)集成測(cè)試,通常 達(dá)到 80% 左右的測(cè)試覆蓋率是有可能的。
MVC 的重要性
因?yàn)?Apple 在所有的實(shí)例項(xiàng)目中都使用了這種模式,加上 Cocoa 本身就是針對(duì)這種模式設(shè)計(jì) 的,所以 Cocoa MVC 成為了 iOS,macOS,tvOS 和 watchOS 上官方認(rèn)證的 app 架構(gòu)模式。
歷史
MVC 這個(gè)名字第一次被提出是在 1979 年,Trygve Reenskaug 用它來(lái)描述 Smalltalk-76 上已 經(jīng)存在的 “template pattern” 應(yīng)用。在他和 Adele Goldberg 討論了術(shù)語(yǔ)方面的問(wèn)題后,MVC 的名字被確定下來(lái) (之前的名字包括 Model-View-Editor 和 Model-View-Tool-Editor 等)。
在原本的構(gòu)想中,view 是直接 “附著” 在 model 層上,并觀察所有 model 變化的。Controller 存在的目的僅僅是捕捉用戶事件,并把它們轉(zhuǎn)發(fā)給 model。這兩個(gè)特性都是 Smalltalk 運(yùn)行方 式的產(chǎn)物,它們并不是為了現(xiàn)代的 app 框架所設(shè)計(jì)的,所以今天這種 MVC 的原始構(gòu)想已經(jīng)幾 乎絕跡了。
Cocoa 中的 MVC 實(shí)現(xiàn)可以追溯到大約 1997 年的 NeXTSTEP 4 的年代。在那之前,所有現(xiàn)在 controller 所擔(dān)當(dāng)?shù)?色,通常都由一個(gè) (像是 NSWindow 那樣的) 高層 view 類(lèi)來(lái)扮演。之后, 從原始的 Smalltalk 的 MVC 實(shí)現(xiàn)中所發(fā)展出的理念是分離展示部分,也就是 view 層和 model 層應(yīng)該被完全隔離開(kāi),這帶來(lái)了一個(gè)強(qiáng)烈的需求,那就是要引入一個(gè)支持對(duì)象來(lái)輔助兩者之間 的通訊。
NeXTSTEP 中 controller 的概念和 Taligent 稍早的 Model-View-Presenter 中的 presenter (展示器) 很相似。不過(guò),在現(xiàn)在 Model-View-Presenter 這個(gè)名字通常被用來(lái)指代那 些通過(guò)協(xié)議從 controller 中將 view 抽象出來(lái)的類(lèi)似 MVC 的模式。
2. Model-View-ViewModel+協(xié)調(diào)器
MVVM 和 MVC 類(lèi)似,也是通過(guò)基于場(chǎng)景 (scene,view 層級(jí)中可能會(huì)在導(dǎo)航發(fā)生改變時(shí)切入或者換出的子樹(shù)) 進(jìn)行的架構(gòu)。相較于 MVC,MVVM 在每個(gè)場(chǎng)景中使用 view-model 來(lái)描述場(chǎng)景中的表現(xiàn)邏輯和交互邏輯。
View-model 在編譯期間不包含對(duì) view 或者 controller 的引用。它暴露出一系列屬性,用來(lái)描 述每個(gè) view 在顯示時(shí)應(yīng)有的值。把一系列變換運(yùn)用到底層的 model 對(duì)象后,就能得到這些最 終可以直接設(shè)置到 view 上的值。實(shí)際將這些值設(shè)置到 view 上的工作,則由預(yù)先建立的綁定來(lái) 完成,綁定會(huì)保證當(dāng)這些顯示值發(fā)生變化時(shí),把它設(shè)定到對(duì)應(yīng)的 view 上去。響應(yīng)式編程是用來(lái) 表達(dá)這類(lèi)聲明和變換關(guān)系的很好的工具,所以它天生就適合 (雖說(shuō)不是嚴(yán)格必要) 被用來(lái)處理
view-model。在很多時(shí)候,整個(gè) view-model 都可以用響應(yīng)式編程綁定的方式,以聲明式的形 式進(jìn)行表達(dá)。
在理論上,因?yàn)?view-model 不包含對(duì) view 層的引用,所以它是獨(dú)立于 app 框架的,這讓對(duì)于 view-model 的測(cè)試也可以獨(dú)立于 app 框架。
由于 view-model 是和場(chǎng)景耦合的,我們還需要一個(gè)能夠在場(chǎng)景間切換時(shí)提供邏輯的對(duì)象。在 MVVM-C 中,這個(gè)對(duì)象叫做協(xié)調(diào)器 (coordinator)。協(xié)調(diào)器持有對(duì) model 層的引用,并且了解 view controller 樹(shù)的結(jié)構(gòu),這樣,它能夠?yàn)槊總€(gè)場(chǎng)景的 view-model 提供所需要的 model 對(duì)象。
和 MVC 不同,MVVM-C 中的 view controller 從來(lái)都不會(huì)直接引用其他的 view controller (所 以,也不會(huì)引用其他的 view-model)。View controller 通過(guò) delegate 的機(jī)制,將 view action 的信息告訴協(xié)調(diào)器。協(xié)調(diào)器據(jù)此顯示新的 view controller 并設(shè)置它們的 model 數(shù)據(jù)。換句話 說(shuō),view controller 的層級(jí)是由協(xié)調(diào)器進(jìn)行管理的,而不是由 view controller 來(lái)決定的。
如果我們忽略掉協(xié)調(diào)器,那么這張圖表就很像 MVC 了,只不過(guò)在 view controller 和 model 之 間加入了一個(gè)階段。MVVM 將之前在 view controller 中的大部分工作轉(zhuǎn)移到了 view-model 中,但是要注意,view-model 并不會(huì)在編譯時(shí)擁有對(duì) view controller 的引用。
View-model 可以從 view controller 和 view 中獨(dú)立出來(lái),也可以被單獨(dú)測(cè)試。同樣,view controller 也不再擁有內(nèi)部的 view state,這些狀態(tài)也被移動(dòng)到了 view-model 中。在 MVC 中 view controller 的雙重?色 (既作為 view 層級(jí)的一部分,又負(fù)責(zé)協(xié)調(diào) view 和 model 之間的交 互),減少到了單一?色 (view controller 僅僅只是 view 層級(jí)的一部分)。
協(xié)調(diào)器模式的加入進(jìn)一步減少了 view controller 所負(fù)責(zé)的部分:現(xiàn)在它不需要關(guān)心如何展示其 他的 view controller 了。因此,這實(shí)際上是以添加了一層 controller 接口為代價(jià),降低了 view controller 之間的耦合。
1.構(gòu)建
對(duì)于 model 的創(chuàng)建和 MVC 中的保持不變,通常它是一個(gè)頂層 controller 的職責(zé)。不過(guò),單獨(dú)
的 model 對(duì)象現(xiàn)在屬于 view-model,而不屬于 view controller。
初始的 view 層級(jí)的創(chuàng)建和 MVC 中的一樣,通過(guò) storyboard 或者代碼來(lái)完成。和 MVC 不同的 是,view controller 不再直接為每個(gè) view 獲取和準(zhǔn)備數(shù)據(jù),它會(huì)把這項(xiàng)工作交給 view-model。View controller 在創(chuàng)建的時(shí)候會(huì)一并創(chuàng)建 view-model,并且將每個(gè) view 綁定到 view-model 所暴露出的相應(yīng)屬性上去。
2.更改 Model
在 MVVM 中,view controller 接收 view 事件的方式和 MVC 中一樣 (在 view 和 view controller 之間建立連接的方式也相同)。不過(guò),當(dāng)一個(gè) view 事件到達(dá)時(shí),view controller 不會(huì) 去改變自身的內(nèi)部狀態(tài)、view state、或者是 model。相對(duì)地,它立即調(diào)用 view-model 上的方 法,再由 view-model 改變內(nèi)部狀態(tài)或者 model。
3.更改 View
和 MVC 不同,view controller 不監(jiān)聽(tīng) model。View-model 將負(fù)責(zé)觀察 model,并將 model 的通知轉(zhuǎn)變?yōu)?view controller 可以理解的形式。View controller 訂閱 view-model 的變更,這 通常通過(guò)一個(gè)響應(yīng)式編程框架來(lái)完成,但也可以使用任意其他的觀察機(jī)制。當(dāng)一個(gè) view-model 事件來(lái)到時(shí),由 view controller 去更改 view 層級(jí)。
為了實(shí)現(xiàn)單向數(shù)據(jù)流,view-model 總是應(yīng)該將變更 model 的 view action 發(fā)送給 model,并 且僅僅在 model 變化實(shí)際發(fā)生之后再通知相關(guān)的觀察者。
4.View State
View state 要么存在于 view 自身之中,要么存在于 view-model 里。和 MVC 不同,view controller 中不存在任何 view state。View-model 中的 view state 的變更,會(huì)被 controller 觀 察到,不過(guò) controller 無(wú)法區(qū)分 model 的通知和 view state 變更的通知。當(dāng)使用協(xié)調(diào)器時(shí), view controller 層級(jí)將由協(xié)調(diào)器進(jìn)行管理。
5.測(cè)試
因?yàn)?view-model 和 view 層與 controller 層是解耦合的,所以可以使用接口測(cè)試來(lái)測(cè)試 view-model,而不需要像 MVC 里那樣使用集成測(cè)試。接口測(cè)試要比集成測(cè)試簡(jiǎn)單得多,因?yàn)?不需要為它們建立完整的組件層次結(jié)構(gòu)。
為了讓接口測(cè)試盡可能覆蓋更多的范圍,view controller 應(yīng)當(dāng)盡可能簡(jiǎn)單,但是那些沒(méi)有被移 出 view controller 的部分仍然需要單獨(dú)進(jìn)行測(cè)試。在我們的實(shí)現(xiàn)中,這部分內(nèi)容包括與協(xié)調(diào)器 的交互,以及初始時(shí)負(fù)責(zé)創(chuàng)建工作的代碼。
MVVM 的重要性
MVVM 是 iOS 上最流行的 MVC 的非直接變形的 app 設(shè)計(jì)模式。換言之,它和 MVC 相比,并沒(méi)有非常大的不同;兩者都是圍繞 view controller 場(chǎng)景構(gòu)建的,而且所使用的機(jī)制也大都相同。
最大的區(qū)別可能在于 view-model 中對(duì)響應(yīng)式編程的使用了,它被用來(lái)描述一系列的轉(zhuǎn)換和依 賴關(guān)系。通過(guò)使用響應(yīng)式編程來(lái)清晰地描述 model 對(duì)象與顯示值之間的關(guān)系,為我們從總體上 理解應(yīng)用中的依賴關(guān)系提供了重要的指導(dǎo)。
iOS 中的協(xié)調(diào)器是一種很有用的模式,因?yàn)楣芾?view controller 層級(jí)是一件非常重要的事情。協(xié)調(diào)器在本質(zhì)上并沒(méi)有和 MVVM 綁定,它也能被使用在 MVC 或者其他模式上。
歷史
MVVM 由 Ken Cooper 和 Ted Peters 提出,他們當(dāng)時(shí)在微軟工作,負(fù)責(zé)后來(lái)變成 Windows Presentation Foundation (WPF) 的項(xiàng)目,這是微軟.NET^[3]^ 的 app 框架,并于 2005 年正式發(fā)布。
WPF 使用一種基于 XML,被稱(chēng)為 XAML 的描述性語(yǔ)言來(lái)描述 view 所綁定的某個(gè) view-model 上的屬性。在 Cocoa 中,沒(méi)有 XAML,我們必須使用像是 RxSwift 這樣的框架和一些 (通常存 在于 controller 中的) 代碼來(lái)完成 view-model 和 view 的綁定。
MVVM 和我們?cè)?MVC 歷史中提到的 MVP 模式非常類(lèi)似. 不過(guò),在 Cooper 和 Peters 的論述中, MVVM 中 view 和 view-model 的綁定需要明確的框架支持,但 presenter 是通過(guò)傳統(tǒng)的手動(dòng) 方式來(lái)傳遞變化。
iOS 中的協(xié)調(diào)器則是最近才 (重新) 流行起來(lái)的,Soroush Khanlou 在 2015 年時(shí)在他的網(wǎng)站上描述了這個(gè)想法。協(xié)調(diào)器基于 app controller 這樣的更古老的模式,而它們?cè)?Cocoa 和其他平臺(tái)上已經(jīng)存在了有數(shù)十年之久。
3. Model-View-Controller+ViewState
MVC+VS 是為標(biāo)準(zhǔn)的 MVC 帶來(lái)單向數(shù)據(jù)流方式的一種嘗試。在標(biāo)準(zhǔn)的 Cocoa MVC 中,view state 可以由兩到三種不同的路徑進(jìn)行操作,MVC+VS 則試圖避免這點(diǎn),讓 view state 的處理 更加易于管理。在 MVC+VS 中,我們明確地在一個(gè)新的 model 對(duì)象中,對(duì)所有的 view state 進(jìn)行定義和表達(dá),我們把這個(gè)對(duì)象叫做 view state model。
在 MVC+VS 中,我們不會(huì)忽略任何一次導(dǎo)航變更,列表選擇,文本框編輯,開(kāi)關(guān)變更,model 展示或者滾動(dòng)位置變更 (或者其他任意的 view state 變化)。我們將這些變更發(fā)送給 view state model。每個(gè) view controller 負(fù)責(zé)監(jiān)聽(tīng) view state model,這樣變更的通訊會(huì)非常直接。在表現(xiàn)或者交互邏輯部分,我們不從 view 中去讀取 view state ,而是從 view state model 中去獲 取它們:
結(jié)果所得到的圖表和 MVC 類(lèi)似,但 controller 的內(nèi)部反饋回路的部分 (被用來(lái)更新 view state) 有所不同,現(xiàn)在它和 model 的回路類(lèi)似,形成了一個(gè)獨(dú)立的 view state 回路。
1.構(gòu)建
和傳統(tǒng)的 MVC 一樣,將文檔 model 數(shù)據(jù)應(yīng)用到 view 上的工作依然是 view controller 的責(zé)任, view controller 還會(huì)使用和訂閱 view state 。因?yàn)?view state model 和文檔 model 都需要觀 察,所以相比于典型的 MVC 來(lái)說(shuō),我們需要多得多的通過(guò)通知進(jìn)行觀察的函數(shù)。
2.更改 Model
當(dāng) view action 發(fā)生時(shí),view controller 去變更文檔 model (這和 MVC 保持不變) 或者變更 model state。我們不會(huì)去直接改變 view 層級(jí),所有的 view 變更都要通過(guò)文檔 model 和 view state model 的通知來(lái)進(jìn)行。
3.更改 View
Controller 同時(shí)對(duì)文檔 model 和 view state model 進(jìn)行觀察,并且只在變更發(fā)生的時(shí)候更新 view 層級(jí)。
View State
View State 被明確地從 view controller 中提取出來(lái)。處理的方法和 model 是一樣的: controller 觀察 view state model,并且對(duì)應(yīng)地更改 view 層級(jí)。
4.測(cè)試
在 MVC+VS 中,我們使用和 MVC 里類(lèi)似的集成測(cè)試,但是測(cè)試本身會(huì)非常不同。所有的測(cè)試 都從一個(gè)空的根 view controller 開(kāi)始,然后通過(guò)設(shè)定文檔 model 和 view state model,這個(gè) 根 view controller 可以構(gòu)建出整個(gè) view 層級(jí)和 view controller 層級(jí)。MVC 的集成測(cè)試中最困 難的部分 (設(shè)定所有的部件) 在 MVC+VS 中可以被自動(dòng)完成。要測(cè)試另一個(gè) view state 時(shí),我 們可以重新設(shè)置全局 view state,所有的 view controller 都會(huì)調(diào)整自身。
一旦 view 層級(jí)被構(gòu)建,我們可以編寫(xiě)兩種測(cè)試。第一種測(cè)試負(fù)責(zé)檢查 view 層級(jí)是不是按照我 們的期望被建立起來(lái),第二種測(cè)試檢查 view action 有沒(méi)有正確地改變 view state。
MVC+VS 的重要性
MVC+VS 主要是用來(lái)對(duì) view state 進(jìn)行教學(xué)的工具。
在一個(gè)非標(biāo)準(zhǔn) MVC 的 app 中,添加一個(gè) view state model,并且在每個(gè) view controller 中 (在已經(jīng)對(duì) model 進(jìn)行觀察的基礎(chǔ)上) 觀察這些 view state model,提供了不少優(yōu)點(diǎn):任意的狀 態(tài)恢復(fù) (這種恢復(fù)不依賴于 storyboard 或者 UIStateRestoration),完整的用戶界面日志,以及 為了調(diào)試目的,在不同的 view state 間進(jìn)行跳轉(zhuǎn)的能力。
歷史
這種特定的體系是 Matt Gallagher 在 2017 年開(kāi)發(fā)的教學(xué)工具,它被用來(lái)展示單向數(shù)據(jù)流和用 戶界面的時(shí)間旅行等概念。這個(gè)模式的目標(biāo)是,在傳統(tǒng)的 Cocoa MVC app 上通過(guò)最小的改動(dòng), 實(shí)現(xiàn)對(duì) view 的狀態(tài)在每個(gè) action 發(fā)生時(shí)都可以進(jìn)行快照。
4. Model 適配器-View 綁定器 (MAVB)
MAVB 是一種以綁定為中心的實(shí)驗(yàn)?zāi)J健T谶@個(gè)模式中,有三個(gè)重要的概念:view 綁定器, model 適配器,以及綁定。
View 綁定器是 view (或者 view controller) 的封裝類(lèi):它構(gòu)建 view,并且為它暴露出一個(gè)綁定 列表。一些綁定為 view 提供數(shù)據(jù) (比如,一個(gè)標(biāo)簽的文本),另一些從 view 中發(fā)出事件 (比如, 按鈕點(diǎn)擊或者導(dǎo)航變更)。
雖然 view 綁定器可以含有動(dòng)態(tài)綁定,但是 view 綁定器本身是不可變的。這讓 MAVB 也成為了 一種聲明式的模式:你聲明 view 綁定器和它們的 action,而不是隨著時(shí)間去改變 view 綁定器。
Model 適配器是可變狀態(tài)的封裝,它是由所謂的 reducer 進(jìn)行實(shí)現(xiàn)的。Model 適配器提供了一 個(gè) (用于發(fā)送事件的) 輸入綁定,以及一個(gè) (用于接收更新的) 輸出綁定。
在 MAVB 中,你不會(huì)去直接創(chuàng)建 view;相對(duì)地,你只會(huì)去創(chuàng)建 view 綁定器。同樣地,你也從 來(lái)不會(huì)去處理 model 適配器以外的可變狀態(tài)。在 view 綁定器和 model 適配器之間的 (兩個(gè)方 向上的) 變換,是通過(guò) (使用標(biāo)準(zhǔn)的響應(yīng)式編程技術(shù)) 來(lái)對(duì)綁定進(jìn)行變形而完成的。
MAVB 移除了對(duì) controller 層的需求。創(chuàng)建邏輯通過(guò) view 綁定器來(lái)表達(dá),變換邏輯通過(guò)綁定來(lái) 表達(dá),而狀態(tài)變更則通過(guò) model 適配器來(lái)表達(dá)。結(jié)果得到的框圖如下:
1.構(gòu)建
Model 適配器 (用來(lái)封裝主 model ) 和 view state 適配器 (封裝頂層的 view state) 通常是在
main.swift 文件中進(jìn)行創(chuàng)建的,這早于任何的 view。
View 綁定器使用普通的函數(shù)進(jìn)行構(gòu)建,這些函數(shù)接受必要的 model 適配器作為參數(shù)。實(shí)際的
Cocoa view 則由框架負(fù)責(zé)進(jìn)行創(chuàng)建。2. 更改 Model
當(dāng)一個(gè) view (或者 view controller) 可以發(fā)出 action 時(shí),對(duì)應(yīng)的 view 綁定允許我們指定一個(gè) action 綁定。在這里,數(shù)據(jù)從 view 流向 action 綁定的輸出端。典型情況下,輸出端會(huì)與一個(gè) model 適配器相連接,view 事件會(huì)通過(guò)綁定進(jìn)行變形,成為 model 適配器可以理解的一條消 息。這條消息隨后被 model 適配器的 reducer 使用,并改變狀態(tài)。
2.更改 View
當(dāng) model 適配器的狀態(tài)發(fā)生改變時(shí),它會(huì)通過(guò)輸出信號(hào)產(chǎn)生通知。在 view 綁定器中,我們可 以將 model 適配器的輸出信號(hào)進(jìn)行變形,并將它綁定到一個(gè) view 屬性上去。這樣一來(lái),view 屬性就會(huì)在一個(gè)通知被發(fā)送時(shí)自動(dòng)進(jìn)行變更了。
3.View State
View state 被認(rèn)為是 model 層的一部分。View state action 以及 view state 通知和 model action 以及 model 通知享有同樣的路徑。
4.測(cè)試
在 MAVB 中,我們通過(guò)測(cè)試 view 綁定器來(lái)測(cè)試代碼。由于 view 綁定器是一組綁定的列表,我 們可以驗(yàn)證綁定包含了我們所期望的條目,而且它們的配置正確無(wú)誤。我們可以和使用綁定來(lái) 測(cè)試初始構(gòu)建以及發(fā)生變化時(shí)的情況。
在 MAVB 中進(jìn)行的測(cè)試,與在 MVVM 中的測(cè)試很相似。不過(guò),在 MVVM 中,view controller 有可能會(huì)包含邏輯,這導(dǎo)致在 view-model 和 view 之間有可能會(huì)存在沒(méi)有測(cè)試到的代碼。而 MAVB 中不存在 view controller,綁定代碼是 model 適配器和 view 綁定器之間的唯一的代碼, 這樣一來(lái),保證完整的測(cè)試覆蓋要簡(jiǎn)單得多。
MAVB 的重要性
在我們所討論的主要模式之中,MAVB 沒(méi)有遵循某個(gè)直接的先例,它既不是從其他平臺(tái)移植過(guò) 來(lái)的模式,也不是其他模式的變種。它自成一派,用于試驗(yàn)?zāi)康模乙恍┢婀帧N覀冊(cè)谶@兒 介紹它的意義在于,它展示了一些很不一樣的東西。不過(guò),這并不是說(shuō)這個(gè)模式?jīng)]有從其他模 式中借鑒經(jīng)驗(yàn)教訓(xùn):像是綁定、響應(yīng)式編程、領(lǐng)域?qū)S谜Z(yǔ)言以及 reducer 都是已經(jīng)被熟知的想 法了。
歷史
MAVB 是 Matt Gallagher 在 Cocoa with Love 網(wǎng)站上首先提出的。這個(gè)模式參照了 Cocoa 綁 定、函數(shù)式響應(yīng)動(dòng)畫(huà)、ComponentKit、XAML、Redux 以及成千上萬(wàn)行的使用 Cocoa view controller 的經(jīng)驗(yàn)。
本書(shū)中的實(shí)現(xiàn)使用了 CwlViews 框架來(lái)處理 view 構(gòu)建、綁定器和適配器的實(shí)現(xiàn)等工作。
5. Elm 架構(gòu) (TEA)
TEA 和 MVC 有著根本上的不同。在 TEA 中,model 和所有的 view state 被集成為一個(gè)單個(gè)狀 態(tài)對(duì)象,所有 app 中的變化都通過(guò)向狀態(tài)對(duì)象發(fā)送消息來(lái)發(fā)生,一個(gè)叫做 reducer 的狀態(tài)更新 函數(shù)負(fù)責(zé)處理這些消息。
在 TEA 中,每個(gè)狀態(tài)的改變會(huì)生成一個(gè)新的虛擬 view 層級(jí),它由輕量級(jí)的結(jié)構(gòu)體組成,描述 了 view 層級(jí)應(yīng)該看上去的形式。虛擬 view 層級(jí)讓我們能夠使用純函數(shù)的方式來(lái)寫(xiě) view 部分 的代碼;虛擬 view 層級(jí)總是直接從狀態(tài)進(jìn)行計(jì)算,中間不會(huì)有任何副作用。當(dāng)狀態(tài)發(fā)生改變 時(shí),我們使用同樣的函數(shù)重新計(jì)算 view 層級(jí),而不是直接去改變 view 層級(jí)。
Driver 類(lèi)型 (這是 TEA 框架中的一部分,它負(fù)責(zé)持有對(duì) TEA 中其他層的引用) 將對(duì)虛擬 view 層 級(jí)和 UIView 層級(jí)進(jìn)行比較,并且對(duì)它進(jìn)行必要的更改,讓 view 和它們的虛擬版本相符合。這 個(gè) TEA 框架中的 driver (驅(qū)動(dòng)) 部件是隨著我們 app 的啟動(dòng)而被初始化的,它自身并不知道要對(duì) 應(yīng)哪個(gè)特定的 app。我們要在它的初始化方法中傳入這些信息:包括 app 的初始狀態(tài),一個(gè)通 過(guò)消息更新?tīng)顟B(tài)的函數(shù),一個(gè)根據(jù)給定狀態(tài)渲染虛擬 view 層級(jí)的函數(shù),以及一個(gè)根據(jù)給定狀態(tài) 計(jì)算通知訂閱的函數(shù) (比如,我們可以訂閱某個(gè) model store 更改時(shí)所發(fā)出的通知)。
從框架的使用者的視?來(lái)看,TEA 的關(guān)于更改部分的框圖是這樣的:
如果我們追蹤這張圖表的上面兩層,我們會(huì)發(fā)現(xiàn)在 view 和 model 之間存在我們?cè)诒菊麻_(kāi)頭是 就說(shuō)過(guò)的反饋回路;這是一個(gè)從 view 到狀態(tài),然后再返回 view 的回路 (通過(guò) TEA 框架進(jìn)行協(xié) 調(diào))。
下面的回路代表的是 TEA中處理副作用的方式 (比如將數(shù)據(jù)寫(xiě)入磁盤(pán)中):當(dāng)在狀態(tài)更新方法中 處理消息時(shí),我們可以返回一個(gè)命令,這些命令會(huì)被 driver 所執(zhí)行。在我們的例子中,最重要 的命令是更改 store 中的內(nèi)容,store 反過(guò)來(lái)又被 driver 所持有的訂閱者監(jiān)聽(tīng)。這些訂閱者可 以觸發(fā)消息來(lái)改變狀態(tài),狀態(tài)最終觸發(fā) view 的重新渲染作為響應(yīng)。
這些事件回路的結(jié)構(gòu)讓 TEA 成為了遵守單向數(shù)據(jù)流原則的設(shè)計(jì)模式的另一個(gè)例子。
1.構(gòu)建
狀態(tài)在啟動(dòng)時(shí)被構(gòu)建,并傳遞給運(yùn)行時(shí)系統(tǒng) (也就是 driver)。運(yùn)行時(shí)系統(tǒng)擁有狀態(tài),store 是一 個(gè)單例。
初始的 view 層級(jí)和之后更新時(shí)的 view 層級(jí)是通過(guò)同樣的路徑構(gòu)建的:通過(guò)當(dāng)前的狀態(tài),計(jì)算 出虛擬 view 層級(jí),運(yùn)行時(shí)系統(tǒng)負(fù)責(zé)更新真實(shí)的 view 層級(jí),讓它與虛擬 view 層級(jí)相匹配。
2.更改 Model
虛擬 view 擁有與它們所關(guān)聯(lián)的消息,這些消息在一個(gè) view 事件發(fā)生時(shí)會(huì)被發(fā)送。Driver 可以 接收這些消息,并使用更新方法來(lái)改變狀態(tài)。更新方法可以返回一個(gè)命令 (副作用),比如我們
想在 store 中進(jìn)行的改動(dòng)。Driver 會(huì)截獲該命令并執(zhí)行它。TEA 讓 view 不可能直接對(duì)狀態(tài)或者 store 進(jìn)行更改。
3.更改 View
運(yùn)行時(shí)系統(tǒng)負(fù)責(zé)這件事。改變 view 的唯一方式是改變狀態(tài)。所以,初始化創(chuàng)建 view 層級(jí)和更
新 view 層級(jí)之間沒(méi)有區(qū)別。4. View State
View state 是包含在整體的狀態(tài)之中的。由于 view 是直接從狀態(tài)中計(jì)算出來(lái)的,導(dǎo)航和交互狀 態(tài)也同樣會(huì)被自動(dòng)更新。
4.測(cè)試
在大多數(shù)架構(gòu)中,讓測(cè)試部件彼此相連往往要花費(fèi)大量努力。在 TEA 中,我們不需要對(duì)此進(jìn)行 測(cè)試,因?yàn)?driver 會(huì)自動(dòng)處理這部分內(nèi)容。類(lèi)似地,我們不需要測(cè)試當(dāng)狀態(tài)變化時(shí) view 會(huì)正確 隨之變化。我們所需要測(cè)試的僅僅是對(duì)于給定的狀態(tài),虛擬 view 層級(jí)可以被正確計(jì)算。
要測(cè)試狀態(tài)的變更,我們可以創(chuàng)建一個(gè)給定的狀態(tài),然后使用 update 方法和對(duì)應(yīng)的消息來(lái)改 變狀態(tài)。然后通過(guò)對(duì)比之前和之后的狀態(tài),我們就可以驗(yàn)證 update 是否對(duì)給定的狀態(tài)和消息 返回了所期望的結(jié)果。在 TEA 中,我們還可以測(cè)試對(duì)應(yīng)給定狀態(tài)的訂閱是不是正確。和 view 層級(jí)一樣,update 函數(shù)和訂閱也都是純函數(shù)。
因?yàn)樗械牟考?(計(jì)算虛擬 view 層級(jí),更新函數(shù)和訂閱) 都是純函數(shù),我們可以對(duì)它們進(jìn)行完 全隔離的測(cè)試。任何框架部件的初始化都是不需要的,我們只用將參數(shù)傳遞進(jìn)去,然后驗(yàn)證結(jié) 果就行了。我們 TEA 實(shí)現(xiàn)中的大多數(shù)測(cè)試都非常直截了當(dāng)。
Elm 架構(gòu)的重要性
TEA 最早是在 Elm 這?函數(shù)式語(yǔ)言中被實(shí)現(xiàn)的。所以 TEA 是一種如何用函數(shù)式的方法表達(dá) GUI 編程的嘗試。TEA 同時(shí)也是最為古老的單向數(shù)據(jù)流架構(gòu)。
歷史
Elm 是 Evan Czaplicki 所設(shè)計(jì)的函數(shù)式編程語(yǔ)言,它最初的目的是為了構(gòu)建前端 web app。TEA 是歸功于 Elm 社區(qū)的一個(gè)模式,它的出現(xiàn)是語(yǔ)言約束和目標(biāo)環(huán)境相互作用的自然結(jié)果。它 背后的思想影響了很多其他的基于 web 的框架,其中包括 React、Redux 和 Flux 等。在 Swift 中,還沒(méi)有 TEA 的權(quán)威實(shí)現(xiàn),不過(guò)我們可以找到不少研究型的項(xiàng)目。在本書(shū)中,我們使用 Swift 按我們自己的理解實(shí)現(xiàn)了這個(gè)模式。主要的工作由 Chris Eidhof 于 2017 年完成。雖然我 們的這個(gè)實(shí)現(xiàn)還并不是 “產(chǎn)品級(jí)” 的,但是許多想法是可以用在生產(chǎn)代碼中的。
三,其他APP架構(gòu)模式
1. Model-View-Presenter
Model-View-Presenter (MVP) 是一種在 Android 上很流行的模式,在 iOS 中,也有相應(yīng)的實(shí) 現(xiàn)。在總體結(jié)構(gòu)和使用的技術(shù)上,它粗略來(lái)說(shuō)是一種位于標(biāo)準(zhǔn) MVC 和 MVVM 之間的模式。
MVP 使用單獨(dú)的 presenter 對(duì)象,它和 MVVM 中 view-model 所扮演的?色一樣。相對(duì) view-model 而言,presenter 去除了響應(yīng)式編程的部分,而是把要展示的值暴露為接口上的屬 性。不過(guò),每當(dāng)這些值需要變更的時(shí)候,presenter 會(huì)立即將它們推送到下面的 view 中去 (view 將自己作為協(xié)議暴露給 presenter)。
從抽象的觀點(diǎn)來(lái)看,MVP 和 MVC 很像。Cocoa 的 MVC,除了名字以外,就是一個(gè) MVP - 它是 從上世紀(jì)九十年代 Taligent 的原始的 MVP 實(shí)現(xiàn)中派生出來(lái)的。View,狀態(tài)和關(guān)聯(lián)的邏輯在兩 個(gè)模式中都是一樣的。不同之處在于,現(xiàn)代的 MVP 中有一個(gè)分離的 presenter 實(shí)體,它使用協(xié) 議來(lái)在 presenter 和 view controller 之間進(jìn)行界定,Cocoa 的 MVC 讓 controller 能夠直接引 用 view,而 MVP 中的 presenter 只能知道 view 的協(xié)議。
有些開(kāi)發(fā)者認(rèn)為協(xié)議的分離對(duì)于測(cè)試是必要的。當(dāng)我們?cè)谟懻摐y(cè)試時(shí),我們會(huì)看到標(biāo)準(zhǔn)的 MVC 在沒(méi)有任何分離的情況下,也可以被完整測(cè)試。所以,我們感覺(jué) MVP 并沒(méi)有太大不同。如果我 們對(duì)測(cè)試一個(gè)完全解耦的展示層有強(qiáng)烈需求的話,我們認(rèn)為 MVVM 的方式更簡(jiǎn)單一些:讓 view controller 通過(guò)觀察去從 view-model 中拉取值,而不是讓 presenter 將值推送到一個(gè)協(xié) 議中去。
2. VIPER,Riblets,和其他 “Clean” 架構(gòu)
VIPER,Riblets 和其他類(lèi)似的模式嘗試將 Robert Martin 的 “Clean Architecture” 從 web app 帶到 iOS 開(kāi)發(fā)中,它們主要把 controller 的職責(zé)分散到三到四個(gè)不同的類(lèi)中,并用嚴(yán)格的順序 將它們排列起來(lái)。在序列中的每個(gè)類(lèi)都不允許直接引用序列中前面的類(lèi)。
為了強(qiáng)制單方向的引用這一規(guī)則,這些模式需要非常多的協(xié)議,類(lèi),以及在不同層中傳遞數(shù)據(jù) 的方式。由于這個(gè)原因,很多使用這些模式的開(kāi)發(fā)者會(huì)去使用代碼生成器。我們的感覺(jué)是,這 些代碼生成器,以及任何的繁雜到需要生成器的模式,都產(chǎn)生了一些誤導(dǎo)。將 “Clean” 架構(gòu)帶 到 Cocoa 的嘗試通常都宣稱(chēng)它們可以管理 view controller 的 “肥大化” 問(wèn)題,但是讓人啼笑皆 非的是,這么做往往讓代碼庫(kù)變得更大。
雖然將接口分解是控制代碼尺寸的一種有效手段,但是我們認(rèn)為這應(yīng)該按需進(jìn)行,而不是教條 式地對(duì)每個(gè) view controller 都這么操作。分解接口需要我們對(duì)數(shù)據(jù)以及所涉及到的任務(wù)有清楚 的認(rèn)識(shí),只有這樣,我們才能達(dá)到最優(yōu)的抽象,并在最大程度上降低代碼的復(fù)雜度。
3. 基于組件的架構(gòu) (React Native)
如果你選擇使用 JavaScript 而不是 Swift 編程,或者你的 app 重度依賴于 web API 的交互, JavaScript 會(huì)是更好的選擇,這時(shí)你可能會(huì)考慮 React Native。不過(guò),本書(shū)是專(zhuān)注于 Swift 和 Cocoa 的,所以我們將探索模式的界限定在了這些領(lǐng)域內(nèi)。
如果你想要找一些類(lèi)似 React Native,但是是基于 Swift 的東西的話,可以看看我們對(duì) TEA 的 探索。MAVB 的實(shí)現(xiàn)也從 ComponentKit 中獲得了一些啟發(fā),而 ComponentKit 本身又從 React 中獲取靈感:它使用類(lèi) DSL 的語(yǔ)法來(lái)進(jìn)行聲明式和可變形的 view 構(gòu)建,這和 React 中 Component 的 render 方法及其相似。
審核編輯:湯梓紅
-
Apple
+關(guān)注
關(guān)注
1文章
925瀏覽量
52758 -
APP
+關(guān)注
關(guān)注
33文章
1569瀏覽量
72384 -
iOS
+關(guān)注
關(guān)注
8文章
3393瀏覽量
150456 -
MVC
+關(guān)注
關(guān)注
0文章
73瀏覽量
13841 -
架構(gòu)設(shè)計(jì)
+關(guān)注
關(guān)注
0文章
31瀏覽量
6921
原文標(biāo)題:iOS APP 架構(gòu)設(shè)計(jì)
文章出處:【微信號(hào):AndroidPush,微信公眾號(hào):Android編程精選】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論