MVC MVVM

1、MVC

從字面意思來理解,MVC 即 Modal View Controller(模型 視圖 控制器),是 Xerox PARC 在 20 世紀 80 年代為編程語言 Smalltalk-80 發明的一種軟件設計模式,至今已廣泛應用于用戶交互應用程序中。其用意在于將數據與視圖分離開來。在 iOS 開發中 MVC 的機制被使用的淋漓盡致,充分理解 iOS 的 MVC 模式,有助于我們程序的組織合理性。

MVC 的幾個明顯的特征和體現:

View 上面顯示什么東西,取決于 Model。

只要 Model 數據改了,View 的顯示狀態會跟著更改。

Control 負責初始化 Model,并將 Model 傳遞給 View 去解析展示。

1)Modal 模型對象:

模型對象封裝了應用程序的數據,并定義操控和處理該數據的邏輯和運算。例如,模型對象可能是表示商品數據 list。用戶在視圖層中所進行的創建或修改數據的操作,通過控制器對象傳達出去,最終會創建或更新模型對象。模型對象更改時(例如通過網絡連接接收到新數據),它通知控制器對象,控制器對象更新相應的視圖對象。

2)View 視圖對象:

視圖對象是應用程序中用戶可以看見的對象。視圖對象知道如何將自己繪制出來,可能對用戶的操作作出響應。視圖對象的主要目的就是顯示來自應用程序模型對象的數據,并使該數據可被編輯。盡管如此,在 MVC 應用程序中,視圖對象通常與模型對象分離。

在iOS應用程序開發中,所有的控件、窗口等都繼承自 UIView,對應 MVC 中的 V。UIView 及其子類主要負責 UI 的實現,而 UIView 所產生的事件都可以采用委托的方式,交給 UIViewController 實現。

3)Controller 控制器對象:

在應用程序的一個或多個視圖對象和一個或多個模型對象之間,控制器對象充當媒介。控制器對象因此是同步管道程序,通過它,視圖對象了解模型對象的更改,反之亦然。控制器對象還可以為應用程序執行設置和協調任務,并管理其他對象的生命周期。

控制器對象解釋在視圖對象中進行的用戶操作,并將新的或更改過的數據傳達給模型對象。模型對象更改時,一個控制器對象會將新的模型數據傳達給視圖對象,以便視圖對象可以顯示它。

對于不同的 UIView,有相應的 UIViewController,對應 MVC 中的 C。例如在 iOS 上常用的 UITableView,它所對應的 Controller 就是UITableViewController。

1.1 簡單的 MVC

控制器加載模型數據并將數據轉換為數據模型。

控制器創建視圖控件,并將模型數據傳遞給視圖控件

1.2 iOS MVC 示意圖

1)Model 和 View 永遠不能相互通信,只能通過 Controller 傳遞。

2)Controller 可以直接與 Model 對話(讀寫調用 Model),Model 通過 Notification 和 KVO 機制與 Controller 間接通信。

3)Controller 可以直接與 View 對話,通過 outlet,直接操作 View,outlet 直接對應到 View 中的控件,View 通過 action 向 Controller 報告事件的發生(如用戶 Touch 我了)。Controller 是 View 的直接數據源(數據很可能是 Controller 從 Model 中取得并經過加工了)。Controller 是 View 的代理(delegate),以同步 View 與 Controller。

1.3 蘋果推薦的 MVC -- 愿景

Cocoa MVC

由于 Controller 是一個介于 View 和 Model 之間的協調器,所以 View 和 Model 之間沒有任何直接的聯系。Controller 是一個最小可重用單元,這對我們來說是一個好消息,因為我們總要找一個地方來寫邏輯復雜度較高的代碼,而這些代碼又不適合放在 Model 中。

理論上來講,這種模式看起來非常直觀,但你有沒有感到哪里有一絲詭異?你甚至聽說過,有人將 MVC 的縮寫展開成 (Massive View Controller),更有甚者,為 View controller 減負也成為 iOS 開發者面臨的一個重要話題。如果蘋果繼承并且對 MVC 模式有一些進展,所有這些為什么還會發生?

1.4 蘋果推薦的 MVC -- 事實

Realistic Cocoa MVC

Cocoa 的 MVC 模式驅使人們寫出臃腫的視圖控制器,因為它們經常被混雜到 View 的生命周期中,因此很難說 View 和 ViewController 是分離的。盡管仍可以將業務邏輯和數據轉換到 Model,但是大多數情況下當需要為 View 減負的時候我們卻無能為力了,View 的最大的任務就是向 Controller 傳遞用戶動作事件。ViewController 不再承擔一切代理和數據源的職責,通常只負責一些分發和取消網絡請求以及一些其他的任務。

你可能會看見過很多次這樣的代碼:

BookModel *bookModel = [myDataArray objectAtIndex:indexPath.row];

[cell configWithModel:bookModel];

這個 cell,正是由 View 直接來調用 Model,所以事實上 MVC 的原則已經違背了,但是這種情況是一直發生的甚至于人們不覺得這里有哪些不對。如果嚴格遵守 MVC 的話,你會把對 cell 的設置放在 Controller 中,不向 View 傳遞一個 Model 對象,這樣就會大大減少 Controller 的體積。Cocoa 的 MVC 被寫成 Massive View Controller 是不無道理的。

直到進行單元測試的時候才會發現問題越來越明顯。因為你的 ViewController 和 View 是緊密耦合的,對它們進行測試就顯得很艱難--你得有足夠的創造性來模擬 View 和它們的生命周期,在以這樣的方式來寫 View Controller 的同時,業務邏輯的代碼也逐漸被分散到 View 的布局代碼中去。

1.5 MVC 自身的不足

MVC 是一個用來組織代碼的權威范式,也是構建 iOS App 的標準模式。Apple 甚至是這么說的。在 MVC 下,所有的對象被歸類為一個 model,一個 view,或一個 controller。Model 持有數據,View 顯示與用戶交互的界面,而 View Controller 調解 Model 和 View 之間的交互。然而,隨著模塊的迭代我們越來越發現 MVC 自身存在著很多不足。

1)MVC 在現實應用中的不足:

在 MVC 模式中 view 將用戶交互通知給控制器。view 的控制器通過更新 Model 來反應狀態的改變。Model(通常使用 Key-Value-Observation)通知控制器來更新他們負責的 view。大多數 iOS 應用程序的代碼使用這種方式來組織。

2)愈發笨重的 Controller:

在傳統的 app 中模型數據一般都很簡單,不涉及到復雜的業務數據邏輯處理,客戶端開發受限于它自身運行的的平臺終端,這一點注定使移動端不像 PC 前端那樣能夠處理大量的復雜的業務場景。然而隨著移動平臺的各種深入,我們不得不考慮這個問題。傳統的 Model 數據大多來源于網絡數據,拿到網絡數據后客戶端要做的事情就是將數據直接按照順序畫在界面上。隨著業務的越來越來的深入,我們依賴的 service 服務可能在大多時間無法第一時間滿足客戶端需要的數據需求,移動端愈發的要自行處理一部分邏輯計算操作。這個時間一慣的做法是在控制器中處理,最終導致了控制器成了垃圾箱,越來越不可維護。

控制器 Controller 是 app 的 “膠水代碼”,協調模型和視圖之間的所有交互。控制器負責管理他們所擁有的視圖的視圖層次結構,還要響應視圖的 loading、appearing、disappearing 等等,同時往往也會充滿我們不愿暴露的 Model 的模型邏輯以及不愿暴露給視圖的業務邏輯。這引出了第一個關于 MVC 的問題...

視圖 view 通常是 UIKit 控件(component,這里根據習慣譯為控件)或者編碼定義的 UIKit 控件的集合。進入 .xib 或者 Storyboard 會發現一個 app、Button、Label 都是由這些可視化的和可交互的控件組成。View 不應該直接引用 Model,并且僅僅通過 IBAction 事件引用 controller。業務邏輯很明顯不歸入 view,視圖本身沒有任何業務。

厚重的 View Controller 由于大量的代碼被放進 viewcontroller,導致他們變的相當臃腫。在 iOS 中有的 view controller 里綿延成千上萬行代碼的事并不是前所未見的。這些超重 app 的突出情況包括:厚重的 View Controller 很難維護(由于其龐大的規模);包含幾十個屬性,使他們的狀態難以管理;遵循許多協議(protocol),導致協議的響應代碼和 controller 的邏輯代碼混淆在一起。

厚重的 view controller 很難測試,不管是手動測試或是使用單元測試,因為有太多可能的狀態。將代碼分解成更小的多個模塊通常是件好事。

3)太過于輕量級的 Model:

早期的 Model 層,其實就是如果數據有幾個屬性,就定義幾個屬性,ARC 普及以后我們在 Model 層的實現文件中基本上看不到代碼(無需再手動管理釋放變量,Model 既沒有復雜的業務處理,也沒有對象的構造,基本上 .m 文件中的代碼普遍是空的);同時與控制器的代碼越來厚重形成強烈的反差,這一度讓人不禁對現有的開發設計構思有所懷疑。

4)遺失的網絡邏輯:

蘋果使用的 MVC 的定義是這么說的:所有的對象都可以被歸類為一個 Model,一個 view,或是一個控制器。就這些,那么把網絡代碼放哪里?和一個 API 通信的代碼應該放在哪兒?

你可能試著把它放在 Model 對象里,但是也會很棘手,因為網絡調用應該使用異步,這樣如果一個網絡請求比持有它的 Model 生命周期更長,事情將變的復雜。顯然也不應該把網絡代碼放在 view 里,因此只剩下控制器了。這同樣是個壞主意,因為這加劇了厚重控制器的問題。那么應該放在那里呢?顯然 MVC 的 3 大組件根本沒有適合放這些代碼的地方。

5)較差的可測試性

MVC 的另一個大問題是,它不鼓勵開發人員編寫單元測試。由于控制器混合了視圖處理邏輯和業務邏輯,分離這些成分的單元測試成了一個艱巨的任務。大多數人選擇忽略這個任務,那就是不做任何測試。

上文提到了控制器可以管理視圖的層次結構;控制器有一個 “view” 屬性,并且可以通過 IBOutlet 訪問視圖的任何子視圖。當有很多 outlet 時這樣做不易于擴展,在某種意義上,最好不要使用子視圖控制器(child view controller)來幫助管理子視圖。在這里有多個模糊的標準,似乎沒有人能完全達成一致。貌似無論如何,view 和對應的 controller 都緊緊的耦合在一起,總之,還是會把它們當成一個組件來對待。Apple 提供的這個組件一度以來在某種程度誤導了大多初學者,初學者將所有的視圖全部拖到 xib 中,連接大量的 IBoutLet 輸出口屬性,都是一些列問題。

一、MVVM是為viewcontroller瘦身?

簡單來說,MVVM的橫空出世是為了解決MVC模式下的viewcontroller”瘦身”。

在MVC模式下,有一個顯著不好的地方,就是viewcontroller即C層,有人稱之為Massive View Controller(臃腫的視圖控制器),為什么稱之為臃腫的視圖控制器?原因其實很簡單,我們一直都把數據請求服務層放到controller,包括一些業務邏輯等將其放在controller,這使得controller代碼量很多,這些堆積的代碼幾乎不被共用,而且維護性很差。MVC和MVVM之間的區別這里不多贅述,后面會專門來講述他們的區別。這篇主要了解MVVM是什么東西。繼續跟我的思路走吧,Let we go !

二、MVVM具體概念

下面我們來看一張圖

我們都知道,在ios這邊,controller(控制器)的全稱是viewcontroller(視圖控制器)。既然是視圖控制器,那么controller就專門負責管理視圖即可,其他的工作就不用負責,在其位某其職即可。那么問題來了,數據請求、業務邏輯層誰負責呢?MVVM就專門構造ViewModel這么一個實體去負責數據請求、業務邏輯這些事情。那么MVVM的具體概念就出來了。

M : 必不可少的Model層,負責數據的存儲

V : viewcontroller,負責管理視圖(自定義)

VM : viewModel,專門負責數據請求、業務邏輯等業務。

工程目錄由

錄進而演化成

那么我們會有一個疑問?viewModel到底是什么?答案是AnyObject!我們都知道刷新UI是在主線程中,那么viewModel就必須與主線程通信(block作為viewmodel封裝的方法的形參),是的,沒錯!那么在V層,之前controller的數據請求和業務邏輯的代碼就全部移植到viewModel里面。在主線程中(controller),就只剩下一些回調方法。這樣的話,controller就真正”瘦身”了。

三、那么viewModel不就是Massive ViewModel?

相信很多人跟我一樣,講到這里都會產生這么一個問題。MVVM中viewmodel做的只是將原本放到controller的數據請求、業務邏輯等代碼放到viewModel里面而已,當業務邏輯比較復雜,那 不是viewModel也很復雜?的確,無可避免。但是,我覺得可以這么解決。

首先,我們重新認識下viewmodel,viewmodel的作用,就是講controller的邏輯層剝離出來,自己去處理邏輯層。(在代碼里面,無非就是子類化一個對象,傳入一些參數,數據進行處理等動作,然后回調到主線程)。功能可拆分子功能,借鑒“組合優先繼承”的思路”去建立多個viewmodel,分工處理。那么在viewmodel里面,就可能存在多個VM,這樣做的好處自然不言而喻咯。

四、關于外界對MVVM的一些評價

首先,我們看下MVVM的作者做它做出的評價

MVVM 的作者 John Gossman 的 批評 應該是最為中肯的。John Gossman 對 MVVM 的批評主要有兩點:

第一點:數據綁定使得 Bug 很難被調試。你看到界面異常了,有可能是你 View 的代碼有 Bug,也可能是 Model 的代碼有問題。數據綁定使得一個位置的 Bug 被快速傳遞到別的位置,要定位原始出問題的地方就變得不那么容易了

第二點:對于過大的項目,數據綁定需要花費更多的內存。MVVM 在使用當中,通常還會利用雙向綁定技術,使得 Model 變化時,ViewModel 會自動更新,而 ViewModel 變化時,View 也會自動變化。所以,MVVM 模式有些時候又被稱作:model-view-binder 模式。

某種意義上來說,我認為就是數據綁定使得 MVVM 變得復雜和難用了。但是,這個缺點同時也被很多人認為是優點。

講下我個人觀點:

(1)MVVM 可以兼容你當下使用的 MVC 架構

(2)MVVM 增加你的應用的可測試性

(3)MVVM 在實際使用中,能夠使得 Model 層和 View 層解耦

(4)MVVM 并未強制我們使用 ReactiveCocoa,MVVM 是一個偉大的典范。(之后會有文章專門講述什么是ReactiveCocoa!)

五、ReactiveCocoa

函數式編程(Functional Programming)和響應式編程(React Programming)也是當前很火的兩個概念,它們的結合可以很方便地實現數據的綁定。于是,在 iOS 編程中,ReactiveCocoa 橫空出世了,它的概念都非常新,包括:

(1)函數式編程(Functional Programming),函數也變成一等公民了,可以擁有和對象同樣的功能,例如當成參數傳遞,當作返回值等。看看 Swift 語言帶來的眾多函數式編程的特性,就你知道這多 Cool 了。

(2)響應式編程(React Programming),原來我們基于事件(Event)的處理方式都弱了,現在是基于輸入(在 ReactiveCocoa 里叫 Signal)的處理方式。輸入還可以通過函數式編程進行各種 Combine 或 Filter,盡顯各種靈活的處理。

(3)無狀態(Stateless),狀態是函數的魔鬼,無狀態使得函數能更好地測試。

(4)不可修改(Immutable),數據都是不可修改的,使得軟件邏輯簡單,也可以更好地測試。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容