iOS MVC、MVVM、MVP架構模式淺淺析

orilme發表於2019-03-17

宣告:本文很多部分是對王巍App 架構一書的學習筆記,如有侵權,請告知

我們需要決定在 app 中如何執行下列任務:

構建 — 誰負責構建 model 和 view,以及將兩者連線起來? 更新 model — 如何處理 view action? 改變 view — 如何將 model 的資料應用到 view 上去? view state — 如何處理導航和其他一些 model state 以外的狀態?

App 的本質是反饋迴路

“View 層和 model 層需要交流。所以,兩者之間需要存在連線。假設 view 層和 model 層是被清晰地分開,而且不存在無法解耦的聯結的話,兩者之間的通訊就需要一些形式的翻譯:”

V和M互動邏輯.png

“當一個 view action 被送到 model 層時,它會被轉變為model action (或者說,讓 model 物件執行一個 action 或者進行更新的命令)。這種命令也被叫做一個訊息 (特別在當 model 是被 reducer 改變時,我們會這麼稱呼它)。將 view action 轉變為 model action 的操作,以及路徑上的其他邏輯被叫做互動邏輯。”

“當 view 依賴於 model 資料時,通知會觸發一個 view 變更,來更改 view 層中的內容。這些通知可以以多種形式存在:Foundation 中的 Notification,代理,回撥,或者是其他機制,都是可以的。將 model 通知和資料轉變為 view 更改的操作,以及路徑上的其他邏輯被叫做表現邏輯。”

MVC

“MVC 的核心思想是,controller 層負責將 model 層和 view 層撮合到一起工作。Controller 對另外兩層進行構建和配置,並對 model 物件和 view 物件之間的雙向通訊進行協調。所以,在一個 MVC app 中,controller 層是作為核心來參與形成 app 的反饋迴路的:”

“圖中的虛線部分代表執行時的引用,view 層和 model 層都不會直接在程式碼中引用 controller。實線部分代表編譯期間的引用,controller 例項知道自己所連線的 view 和 model 物件的介面”

MVC.png

MVC 中有兩個最常見的問題

  • 觀察者模式失效 第一個問題是,model 和 view 的同步可能失效。當圍繞 model 的觀察者模式沒有被完美執行時,這個問題就會發生。常見的錯誤是,在構建 view 時讀取了 model 的值,而沒有對後續的通知進行訂閱。另一個常見錯誤是在變更 model 的同時去更改 view 層級,這種做法假設了變更的結果,而沒有等待 model 進行通知,如果 model 拒絕了這個變更的話,就會發生錯誤。這類錯誤會使得 view 和 model 不同步,奇怪的行為也隨之而來。
  • 肥大的 View Controller View controller 需要負責處理 view 層 (設定 view 屬性,展示 view 等),但是它同時也負責 controller 層的任務 (觀察 model 以及更新 view),最後,它還要負責 model 層 (獲取資料,對其變形或者處理)。結合它在架構中的中心角色,這使得我們很容易在不經意間把所有的職責都賦予 view controller,從而迅速讓程式變得難以管理。

MVC構建

  1. 構建 App 物件負責建立最頂層的 view controller,這個 view controller 將載入 view,並且知道應該從 model 中獲取哪些資料,然後把它們顯示出來。Controller 要麼顯式地建立和持有 model 層,要麼通過一個延遲建立的 model 單例來獲取 model。在多文件配置中,model 層由更低層的像是 UIDocument 或 NSDocument 所擁有。那些和 view 相關的單個 model 物件,通常會被 controller 所引用並快取下來。
  2. 更改 Model 在 MVC 中,controller 主要通過 target/action 機制和 (由 storyboard 或者程式碼進行設定的) delegate 來接收 view 事件。Controller 知道自己所連線的 view,但是 view 在編譯期間卻沒有關於 controller 介面的資訊。當一個 view 事件到達時,controller 有能力改變自身的內部狀態,更改 model,或者直接改變 view 層級。
  3. 更改 View” “在我們所理解的 MVC 中,當一個更改 model 的 view action 發生時,controller 不應該直接去操作 view 層級。正確的做法是,controller 去訂閱 model 通知,並且在當通知到達時再更改 view 層級。這樣一來,資料流就可以單向進行:view action 被轉變為 model 變更,然後 model 傳送通知,這個通知最後被轉為 view 變更。”
  4. View State View state 可以按需要被 store 在 view 或者 controller 的屬性中。相對於影響 model 的 view action,那些隻影響 view 或 controller 狀態的 action 則不需要通過 model 進行傳遞。對於 view state 的儲存,可以結合使用 storyboard 和 UIStateRestoring 來進行實現,storyboard 負責記錄活躍的 controller 層級,“而 UIStateRestoring 負責從 controller 和 view 中讀取資料。”

MVVM

“MVVM 構建的方式和 MVC 的模式很相似:controller 層充分了解程式的結構,它使用這些認知來對所有部件進行構建和連線。 MVVM 與 MVC 最大的區別可能在於 view-model 中對響應式程式設計的使用了,它被用來描述一系列的轉換和依賴關係。通過使用響應式程式設計來清晰地描述 model 物件與顯示值之間的關係,為我們從總體上理解應用中的依賴關係提供了重要的指導。 具體主要有三個不同:

  1. 必須建立 view-model。
  2. 必須建立起 view-model 和 view 之間的繫結。
  3. Model 由 view-model 擁有,而不是由 controller 所擁有。”

“MVVM 通常要求 controller 必須非常簡單 (甚至簡單到無需考慮)。另外,controller 必須儘可能地使用庫提供的繫結方法。在這樣的規則的保證下,理想情況中我們就不需要測試 controller 了,因為它沒有包含我們自己的任何邏輯。究竟應該在 controller 中留存多少邏輯,根本上來說是掌握在程式設計師手上的。”

“MVVM 通過將 model 觀察的程式碼以及其他顯示和互動邏輯移動到圍繞著資料流構建的隔離的類中,解決了 MVC controller 裡不規則的狀態互動所帶來的有關問題。因為這是 MVC 中最顯著的問題,而且會隨著 Cocoa controller 的增大而惡化,這個變化在很大程度上緩解了 MVC 中 controller 肥大的問題。但是還有其他一些因素會使得 controller (以及 view-model) 變大,所以為了可持續發展,重構依然還是有需要的”

MVVM.png

MVVM的構建

  1. 構建 和 MVC 不同的是,view controller 不再直接為每個 view 獲取和準備資料,它會把這項工作交給 view-model。View controller 在建立的時候會一併建立 view-model,並且將每個 view 繫結到 view-model 所暴露出的相應屬性上去。
  2. 更改 Model 在 MVVM 中,view controller 接收 view 事件的方式和 MVC 中一樣 (在 view 和 view controller 之間建立連線的方式也相同)。不過,當一個 view 事件到達時,view controller 不會去改變自身的內部狀態、view state、或者是 model。相對地,它立即呼叫 view-model 上的方法,再由 view-model 改變內部狀態或者 model。
  3. 更改 View 和 MVC 不同,view controller 不監聽 model。View-model 將負責觀察 model,並將 model 的通知轉變為 view controller 可以理解的形式。View controller 訂閱 view-model 的變更,這通常通過一個響應式程式設計框架來完成,但也可以使用任意其他的觀察機制。當一個 view-model 事件來到時,由 view controller 去更改 view 層級。 為了實現單向資料流,view-model 總是應該將變更 model 的 view action 傳送給 model,並且僅僅在 model 變化實際發生之後再通知相關的觀察者。
  4. View State View state 要麼存在於 view 自身之中,要麼存在於 view-model 裡。和 MVC 不同,view controller 中不存在任何 view state。View-model 中的 view state 的變更,會被 controller 觀察到,不過 controller 無法區分 model 的通知和 view state 變更的通知。當使用協調器時,view controller 層級將由協調器進行管理。
  5. 測試 因為 view-model 和 view 層與 controller 層是解耦合的,所以可以使用介面測試來測試 view-model,而不需要像 MVC 裡那樣使用整合測試。介面測試要比整合測試簡單得多,因為不需要為它們建立完整的元件層次結構。 為了讓介面測試儘可能覆蓋更多的範圍,view controller 應當儘可能簡單,但是那些沒有被移出 view controller 的部分仍然需要單獨進行測試。在我們的實現中,這部分內容包括與協調器的互動,以及初始時負責建立工作的程式碼。”

關於view-model

  • 實現狀態恢復資料 在狀態恢復的方法上,MVVM 中為各個 controller 所儲存的資料來源於 view-model,而非像 MVC 那樣來源於 controller 本身,除此之外,兩者所使用的策略大抵相同。

  • View-model 雖然名字裡既有 view 又有 model,但是它所扮演的其實是不折不扣的類似 controller 的角色。

  • View-model 從 view controller 和 view 中獨立出來,也可以被單獨測試。同樣,view controller 也不再擁有內部的 view state,這些狀態也被移動到了 view-model 中。在 MVC 中 view controller 的雙重角色 (既作為 view 層級的一部分,又負責協調 view 和 model 之間的互動),減少到了單一角色 (view controller 僅僅只是 view 層級的一部分)。

  • “View-model 在編譯期間不包含對 view 或者 controller 的引用。它暴露出一系列屬性,用來描述每個 view 在顯示時應有的值。把一系列變換運用到底層的 model 物件後,就能得到這些最終可以直接設定到 view 上的值。實際將這些值設定到 view 上的工作,則由預先建立的繫結來完成,繫結會保證當這些顯示值發生變化時,把它設定到對應的 view 上去。響應式程式設計是用來表達這類宣告和變換關係的很好的工具,所以它天生就適合 (雖說不是嚴格必要) 被用來處理 view-model。在很多時候,整個 view-model 都可以用響應式程式設計繫結的方式,以宣告式的形式進行表達。

  • “在理論上,因為 view-model 不包含對 view 層的引用,所以它是獨立於 app 框架的,這讓對於 view-model 的測試也可以獨立於 app 框架。”

MVVM-C

“在理論上,因為 view-model 不包含對 view 層的引用,所以它是獨立於 app 框架的,這讓對於 view-model 的測試也可以獨立於 app 框架。

由於 view-model 是和場景耦合的,我們還需要一個能夠在場景間切換時提供邏輯的物件。在 MVVM-C 中,這個物件叫做協調器 (coordinator)。協調器持有對 model 層的引用,並且瞭解 view controller 樹的結構,這樣,它能夠為每個場景的 view-model 提供所需要的 model 物件。”

和 MVC 不同,MVVM-C 中的 view controller 從來都不會直接引用其他的 view controller (所以,也不會引用其他的 view-model)。View controller 通過 delegate 的機制,將 view action 的資訊告訴協調器。協調器據此顯示新的 view controller 並設定它們的 model 資料。換句話說,view controller 的層級是由協調器進行管理的,而不是由 view controller 來決定的。如果我們忽略掉協調器,那麼這張圖表就很像 MVC 了,只不過在 view controller 和 model 之間加入了一個階段。MVVM 將之前在 view controller 中的大部分工作轉移到了 view-model 中,但是要注意,view-model 並不會在編譯時擁有對 view controller 的引用。

“初步印象來說,因為 MVVM-C 加入了額外的一層來進行管理,看起來是比 Cocoa MVC 模式更加複雜。不過,在實現的層級,如果你能夠始終如一地貫徹這個模式,程式碼會變得更簡單一些。啊,這裡說的簡單並不意味著容易,只有當你對常見的響應式程式碼變形熟悉以後,才不會對書寫程式碼感到無從下手,才不會對除錯問題感到懊惱沮喪。不過,從令人高興的一面來說,精心設計的資料管道通常不容易產生錯誤,在長期來看維護也更容易一些。”

MVVM-C.png

“iOS 中的協調器是一種很有用的模式,因為管理 view controller 層級是一件非常重要的事情。協調器在本質上並沒有和 MVVM 繫結,它也能被使用在 MVC 或者其他模式上。”

協調器為每個 controller 的 view-model 設定初始的 model 物件。 View-model 將設定值和其他 model 資料及觀察量進行合併。 View-model 將資料變形為 view 所需要的精確的格式。 Controller 將準備好的值繫結到各個 view 上去。

MVP

對於Controller層過於臃腫的問題,MVP模式則能較好地解決這個問題——既然UIViewController和UIView是耦合的,索性把這兩者都歸為View層,業務邏輯則獨立存在於Presenter層,Model層保持不變。下圖比較清除得展示了MVP模式的結構

螢幕快照 2018-10-04 00.09.48.png

MVC-VS

MVC+VS.png

MAVB

“model 介面卡 - view 繫結器 (ModelAdapter-ViewBinder, MAVB)”

MAVB.png

其他模式

Elm 架構 (TEA)、VIPER、Riblets

響應式程式設計

“響應式程式設計是一種用來交流變更的工具,不過和通知或者 KVO 不同的是,它專注於在源和目標之間進行變形,讓邏輯可以在部件之間傳輸資訊的同時得以表達。” “響應式程式設計是一種用來描述資料來源和資料消費端之間資料流動的模式” “資料變形的部分是響應式程式設計所能帶來的最大優勢,但同時它也是學習曲線最為陡峭的部分。”

相關文章