本文從細節入手,嘗試分析了幾種常見的 GUI 架構:MVC、MVCS、MVP、MVVM。對於在實際開發中如何選擇給出了一些參考意見。html
本文同時發表於個人我的博客react
移動開發架構,不管是 iOS、Andriod 仍是 Web 都屬於 GUI (Graphical User Interfaces) 架構範疇。2006年,Martin Fowler 的 GUI Architectures 一文可謂是經典之做。文中 Martin Fowler 提到 MVC 模式如何組織代碼、劃分模塊職責,還提到 Data Binding、Flow Synchronization 以及 Observer Synchronization 等核心概念。 縱觀十年來 GUI 架構演變,不管是 MVCS、MVP 仍是 MVVM,其實討論的核心問題仍是如何分層、如何劃分模塊職責、作好代碼隔離。ios
談到 iOS 上常見架構,相信只要有半年以上開發經驗的同窗都能侃侃而談。但在實際交流過程當中發現很多同窗對關鍵細節問題卻認知模糊,甚至是錯誤的。所以,本文嘗試從細節入手對幾種常見架構進行簡單描述(對架構的認識智者見智、仁者見仁,我所描述的也不必定是正確的)。git
上文提到各類架構雖各類不一樣,但它們其實都是在討論一個問題:『如何分層』。 那麼在繼續以前,咱們有必要思考一下:爲何要分層?github
計算機界有一句大道至簡的名言:設計模式
All problems in computer science can be solved by another level of indirection.安全
之因此要分層,最終目的是下降系統總體複雜度。 經過分層咱們至少能得到如下能力:網絡
說到分層,可能最早想到的例子是 OSI 七層或 TCP/IP 五層網絡模型: 架構
在分層網絡模型中,不一樣協議工做在不一樣網絡層,互不干擾,又協調有致,如:IP 協議工做在網絡層,主要職責是網絡尋址;TCP 協議工做在傳輸層,主要負責創建可靠的網絡鏈接、負責擁塞控制等。正是由於良好的分層,IP 協議無需關心 TCP 協議的工做,反之亦然。同時 IP 協議也能夠在 TCP、UDP 協議間複用。現在對網絡安全愈來愈重視,HTTPS 協議也慢慢普及,經過分層,只需在原有 HTTP 協議基礎上添加一個 TLS/SSL 的安全傳輸層便可,由它來負責加解密,而原有的 HTTP、TCP 協議無需修改: app
MVC(Model-View-Controller)做爲最經典的架構,廣爲人熟知,也是 Apple 官方推薦的移動架構。
MVC模式的核心思想是數據層(Domain)與表現層(Presentation)的隔離。那麼,在數據與展示被隔離以後,它們之間如何同步數據、狀態? 這就涉及 MVC 模式另外一個重要思想:觀察者同步(Observer Synchronization)。Separated Presentation: Ensure that any code that manipulates presentation only manipulates presentation, pushing all domain and data source logic into clearly separated areas of the program.
Observer Synchronization: Synchronize multiple screens by having them all be observers to a shared area of domain data.
經常使用方法:在Presentation Object(Controller)中註冊通知、設置delegate、傳遞block等。當數據須要更新時,Domain Object(Model)經過上述方式將數據自底向上的同步給Presentation Object。
下面簡單介紹一下 Model、View、Controller:
Apple: Model Objects Encapsulate Data and Basic Behaviors. Stanford: Model = What your application is (but not how it is displayed). 簡單講:Model = Data + Manipulate Data
(ps:本文中的Stanford表示斯坦福的 iOS 公開課)
如:咱們書架的 Model:QRBookShelfModel
,包含了數據:
NSArray<QRBookShelfItem *> *books
以及對數據的操做:
addBook:
、
deleteBook:
等。
Apple: Controller Objects Tie the Model to the View. Stanford: Controller = How your Model is presented to the user(UI logic).
Controller 是 Model 與 View 間的鏈接器,其核心職責有:
這裏有個問題:到底什麼是展現邏輯? 簡單講:將業務數據轉換成UI數據,如:
Apple: View Objects Present Information to the User Stanford: View=Your Controller’s minions
總之,View 只作一件事:layout。
爲了實現 MVC 的核心思想:業務 (model) 與展現 (View) 的隔離,必須嚴格遵照一些規則:
看到這裏,你們有沒有一種熟悉的味道? 沒錯,UITableView 與外界 (Contoller) 的交互與此處的描述高度一致。
(關於 MVC 規則的描述,你們也能夠參考 Stanford iOS 公開課中的相關內容)此時,你們或許心中有些疑問: 一、在 MVC 模式中,網絡請求、數據存儲誰來完成? 二、Model、View、Controller 誰的可複用性最強? 三、展現邏輯爲何由 Controller 完成而不是 View?
在 MVC 模式中,網絡請求、數據存儲誰來完成? Controller、Model 均可以,通常由 Model 完成。此時的 Model 已再也不是簡單的 Model Object,而是 Model layer,在咱們項目中一般將其稱爲 Manager。
Model、View、Controller 誰的可複用性最強? View>Model>Controller
展現邏輯爲何由 Controller 完成而不是 View? View可複用性高,不該關心具體展現邏輯,只專一於 layout
MVC 模式被批評最多的就是 Controller 過於臃腫,那麼 Controller 都作了什麼?
尤爲是現在不少產品經理『擅長』作加法,頁面、交互愈來愈複雜,這對於 Controller 來講無疑是雪上加霜。
前面提到,在 MVC 模式中,並無討論獲取數據屬於哪一個模塊的職責 (通常由 model 負責)。MVCS 模式就是在 MVC 基礎上將數據單獨提取爲一層(Store)。
在 MVC 模式中,展現邏輯被劃分爲 Controller 的職責範圍。現在,展現邏輯愈來愈複雜,Controller 隨之也變得愈來愈臃腫。同時,Controller 也被認爲是 View 的一部分,這樣 Model 與 View 間並無徹底隔離、解耦。 MVP (Model-View-Presenter) 就是在這樣的背景下產生的,其將展現邏輯提取爲一個單獨的層(Presenter),簡化了 Controller,也完全隔離了 Model 與 View。
新產生的 Presenter 層有如下特色:最近兩三年對 MVVM(Model View View-Model) 的討論比較多,其提出的願景也是爲了簡化 Controller、完全將 View 與 Model 解耦、並提供 Data Binding。
在 MVVM 中 Controller 被認爲是 View,更準確的說是:在 MVVM 模式中,各模塊間的依賴關係、數據流向、數據傳遞的格式都有嚴格的規定:
如上圖所示,View、View Model 以及 Model 須要遵照如下規則:View(UIViewController/UIView): 1.能夠依賴(持有) View、View Model,便可直接調用其方法; 2.不能依賴(持有) Model、Model Object(Item); 3.UI 綁定到View Model上(如:titleLabel.text->viewModel.title) 4.經過 RACCommands/RACActions 或直接調用 View Model 的方法將用戶事件傳遞給 View Model。
View Model: 1.能夠依賴(持有) View Model、Model 以及 Model Object; 2.不能依賴(持有) View、Raw Model Object; 3.其公開屬性只能是基礎數據類型(NSInteger、NSString等)或其餘 View Model; 4.將 Model Object 轉換成可直接在 View 上顯示的屬性或Sub View Model(展現邏輯); 5.接受來自 View 或 Sub View Model 的輸入(用戶事件)。
Model (Layer): 1.能夠依賴(持有) 其餘 Model、Model Object、Data Source、Raw Model Object; 2.不能依賴(持有) View Model、View; 3.將 Raw Model Object 轉換爲 Model Object; 4.爲 View Model 提供數據(異步)。
其中,View 與 View Model 相似 UIView 與 CALayer 的關係,一一對應(包括層次結構):
從 Data Source 到 Model、Model 到 View Model 可採用通常的同步方法,如:Delegate、Notification 以及 block 等。而從 ViewModel 到 View 的 Data Binding 是 MVVM 模式與其餘 MV* 模式最大的區別。
遺憾的是 iOS 並無原生的 Data Binding 方式,目前大概只能經過兩種方式實現 Data Binding:KVO 或 ReactiveCocoa。KVO/RAC是一種更加激進的 Observer Synchronization:
MVVM 與 MVP 有不少類似的點:
二者最大的區別在於:MVVM 有 Data Binding 而 MVP 沒有。
根據是否有 Data Binding,可將常見 GUI 構架分爲兩大陣營:
MVVM 的 Data Binding 在必定程度上增長了編碼的複雜度、數據流也變得不夠直觀、調試難度也有所增長。但對於數據可變的場景,一旦經過 Data Binding 將 View 與 View Model 綁定起來,在數據變化時,會自動映射到 UI 上,十分方便。
根據展現邏輯是否獨立於 Controller,可分爲:
MVVM、MVP 分別將展現邏輯從 Controller 中提取出來,使 Controller 獲得必定程度的簡化,在展現邏輯複雜的狀況下,效果更加明顯。
經過上述分析,咱們能夠看到,常見幾種架構:MVC、MVCS、MVP、MVVM 並無絕對的好壞之分,只是各有不一樣的適用場景。 咱們在選擇時能夠根據如下兩點做爲參考依據:
MVCS、MVP、MVVM 等各類新生架構,雖各有不一樣,但都是源自於 MVC,它們的核心思想一直沒變,也不能變:
理論的東西講了很多,下面結合實際的項目,看看應該如何選擇架構。
書籍詳情頁在整個 QQ 閱讀 app 中,不管是展現仍是業務邏輯都是最複雜的一個模塊。
所以,該模塊採用 MVVM 架構比較合適。遺憾的是,當時設計該模塊時沒有充分意識到其複雜程序,而是選擇了傳統的 MVC 架構。結果形成詳情頁 Controller 十分複雜,下面這段就是 Controller 中根據下載狀態修改 toolbar 上3個按鈕狀態的代碼(ps:看不清不要緊,只要能看出其很複雜便可^_^):
同時,大量的展現邏輯也耦合在了各個 View 中: 若是,採用 MVVM 架構,各類展現邏輯能夠放到相應的 View Model 中,讓 View 只專一於 layout。同時在 View Model 中處理下載相關邏輯,使下載邏輯與 View 解耦。信息流做爲 QQ 閱讀一大亮點,能爲用戶個性化推薦書籍,是整個 app 中最重要的一個頁面:
信息流是最重要的頁面,但不是最複雜的頁面:所以,信息流模塊不必使用複雜的 MVVM 架構。
不少場景屬於此類情形:經過 UITableView 列舉多行靜態數據,若數據有更新時直接 reload tableview。
各類分層架構都是前輩充滿智慧的寶貴經驗,值得尊敬、借鑑、學習,但也沒必要拘泥於形式,重點是理解其背後的思想。設計模式有六大原則:
其中除了里氏代換原則,其餘五大原則都是分層架構的指導思想。只要咱們深入理解並能嚴格遵照這些原則,不管咱們選擇哪一種架構、或在其基礎上進行衍化,都能設計出高質量的代碼。
代碼設計、架構選擇及理解仁者見仁、智者見智,但經典的設計理念是公認的、也是通過時間檢驗的。