MVC 的歷史
MVC,全稱是 Model View Controller锉罐,是模型 (model)-視圖 (view)-控制器 (controller) 的縮寫。它表示的是一種常見的客戶端軟件開發(fā)框架。
MVC 的概念最早出現(xiàn)在二十世紀八十年代的 施樂帕克 實驗室中(對敬矩,就是那個發(fā)明圖形用戶界面和鼠標的實驗室),當時施樂帕克為 Smalltalk 發(fā)明了這種軟件設計模式钥星。
現(xiàn)在,MVC 已經成為主流的客戶端編程框架,在 iOS 開發(fā)中秒紧,系統(tǒng)為我們實現(xiàn)好了公共的視圖類:UIView,和控制器類:UIViewController僻孝。大多數時候消略,我們都需要繼承這些類來實現(xiàn)我們的程序邏輯,因此章钾,我們幾乎逃避不開 MVC 這種設計模式墙贱。
但是,幾十年過去了贱傀,我們對于 MVC 這種設計模式真的用得好嗎惨撇?其實不是的,MVC 這種分層方式雖然清楚府寒,但是如果使用不當魁衙,很可能讓大量代碼都集中在 Controller 之中,讓 MVC 模式變成了 Massive View Controller 模式株搔。
Controller 的臃腫問題何解剖淀?
很多人試圖解決 MVC 這種架構下 Controller 比較臃腫的問題。
我們來看看 MVC 這種架構的特點纤房。其實設計模式很多時候是為了 Don't repeat yourself 原則來做的纵隔,該原則要求能夠復用的代碼要盡量復用,來保證重用炮姨。在 MVC 這種設計模式中捌刮,我們發(fā)現(xiàn) View 和 Model 都是符合這種原則的。
對于 View 來說舒岸,你如果抽象得好绅作,那么一個 App 的動畫效果可以很方便地移植到別的 App 上,而 Github 上也有很多 UI 控件蛾派,這些控件都是在 View 層做了很好的封裝設計棚蓄,使得它能夠方便地開源給大家復用。
對于 Model 來說碍脏,它其實是用來存儲業(yè)務的數據的梭依,如果做得好,它也可以方便地復用典尾。當然役拴,Model 層的復用大多數是在一個產品內部,不太可能像 View 層那樣開源給社區(qū)钾埂。
說完 View 和 Model 了河闰,那我們想想 Controller科平,Controller 有多少可以復用的?我們寫完了一個 Controller 之后姜性,可以很方便地復用它嗎瞪慧?結論是:非常難復用。在某些場景下部念,我們可能可以用 addSubViewController 之類的方式復用 Controller弃酌,但它的復用場景還是非常非常少的。
如果我們能夠意識到 Controller 里面的代碼不便于復用儡炼,我們就能知道什么代碼應該寫在 Controller 里面了妓湘,那就是那些不能復用的代碼。在我看來乌询,Controller 里面就只應該存放這些不能復用的代碼榜贴,這些代碼包括:
在初始化時,構造相應的 View 和 Model妹田。
監(jiān)聽 Model 層的事件唬党,將 Model 層的數據傳遞到 View 層。
監(jiān)聽 View 層的事件鬼佣,并且將 View 層的事件轉發(fā)到 Model 層初嘹。
如果 Controller 只有以上的這些代碼,那么它的邏輯將非常簡單沮趣,而且也會非常短屯烦。
但是,我們卻很難做到這一點房铭,因為還是有很多邏輯我們不知道寫在哪里驻龟,于是就都寫到了 Controller 中了,那我們接下來就看看其它邏輯應該寫在哪里缸匪。
如何對 ViewController 瘦身翁狐?
objc.io 是一個非常有名的 iOS 開發(fā)博客,它上面的第一課 《Lighter View Controllers》 上就講了很多這樣的技巧凌蔬,我們先總結一下它里面的觀點:
將 UITableView 的 Data Source 分離到另外一個類中露懒。
將數據獲取和轉換的邏輯分別到另外一個類中。
將拼裝控件的邏輯砂心,分離到另外一個類中懈词。
你想明白了嗎?其實 MVC 雖然只有三層辩诞,但是它并沒有限制你只能有三層坎弯。所以,我們可以將 Controller 里面過于臃腫的邏輯抽取出來,形成新的可復用模塊或架構層次抠忘。
我個人對于邏輯的抽取撩炊,有以下總結。
將網絡請求抽象到單獨的類中
新手寫代碼崎脉,直接就在 Controller 里面用 AFNetworking 發(fā)一個請求拧咳,請求的完數據直接就傳遞給 View。入門一些的同學囚灼,知道把這些請求代碼移到另外一個靜態(tài)類里面骆膝。但是我覺得還不夠,所以我建議將每一個網絡請求直接封裝成類啦撮。
把每一個網絡請求封裝成對象其實是使用了設計模式中的 Command 模式,它有以下好處:
將網絡請求與具體的第三方庫依賴隔離汪厨,方便以后更換底層的網絡庫赃春。
方便在基類中處理公共邏輯。
方便在基類中處理緩存邏輯劫乱,以及其它一些公共邏輯织中。
方便做對象的持久化。
將界面的拼裝抽象到專門的類中
新手寫代碼衷戈,喜歡在 Controller 中把一個個 UILabel 狭吼,UIButton,UITextField 往 self.view 上用 addSubView 方法放殖妇。我建議大家可以用兩種辦法把這些代碼從 Controller 中剝離刁笙。
方法一:構造專門的 UIView 的子類,來負責這些控件的拼裝谦趣。這是最徹底和優(yōu)雅的方式疲吸,不過稍微麻煩一些的是,你需要把這些控件的事件回調先接管前鹅,再都一一暴露回 Controller摘悴。
方法二:用一個靜態(tài)的 Util 類,幫助你做 UIView 的拼裝工作舰绘。這種方式稍微做得不太徹底蹂喻,但是比較簡單。
對于一些能復用的 UI 控件捂寿,我建議用方法一口四。如果項目工程比較復雜,我也建議用方法一秦陋。如果項目太緊窃祝,另外相關項目的代碼量也不多,可以嘗試方法二。
構造 ViewModel
誰說 MVC 就不能用 ViewModel 的粪小?MVVM 的優(yōu)點我們一樣可以借鑒大磺。具體做法就是將 ViewController 給 View 傳遞數據這個過程,抽象成構造 ViewModel 的過程探膊。
這樣抽象之后杠愧,View 只接受 ViewModel,而 Controller 只需要傳遞 ViewModel 這么一行代碼逞壁。而另外構造 ViewModel 的過程流济,我們就可以移動到另外的類中了。
在具體實踐中腌闯,我建議大家專門創(chuàng)建構造 ViewModel 工廠類绳瘟,參見 工廠模式。另外姿骏,也可以專門將數據存取都抽將到一個 Service 層糖声,由這層來提供 ViewModel 的獲取。
專門構造存儲類
剛剛說到 ViewModel 的構造可以抽獎到一個 Service 層分瘦。與此相應的蘸泻,數據的存儲也應該由專門的對象來做。在小猿搜題項目中嘲玫,我們由一個叫 UserAgent 的類悦施,專門來處理本地數據的存取。
數據存取放在專門的類中去团,就可以針對存取做額外的事情了抡诞。比如:
對一些熱點數據增加緩存
處理數據遷移相關的邏輯
如果要做得更細,可以把存儲引擎再抽象出一層土陪。這樣你就可以方便地切換存儲的底層沐绒,例如從 sqlite 切換到 key-value 的存儲引擎等。
小結
通過代碼的抽取旺坠,我們可以將原本的 MVC 設計模式中的 ViewController 進一步拆分乔遮,構造出 網絡請求層、ViewModel 層取刃、Service 層蹋肮、Storage 層等其它類,來配合 Controller 工作璧疗,從而使 Controller 更加簡單坯辩,我們的 App 更容易維護。
另外崩侠,不知道大家注意到沒漆魔,其實 Controller 層是非常難于測試的,如果我們能夠將 Controller 瘦身,就可以更方便地寫 Unit Test 來測試各種與界面的無關的邏輯改抡。移動端自動化測試框架都不太成熟矢炼,但是將 Controller 的代碼抽取出來,是有助于我們做測試工作的阿纤。
MVVM 的歷史
MVVM 是 Model-View-ViewModel 的簡寫句灌。
相對于 MVC 的歷史來說,MVVM 是一個相當新的架構欠拾,MVVM 最早于 2005 年被微軟的 WPF 和 Silverlight 的架構師 John Gossman 提出胰锌,并且應用在微軟的軟件開發(fā)中。當時 MVC 已經被提出了 20 多年了藐窄,可見兩者出現(xiàn)的年代差別有多大资昧。
MVVM 在使用當中,通常還會利用雙向綁定技術荆忍,使得 Model 變化時格带,ViewModel 會自動更新,而 ViewModel 變化時东揣,View 也會自動變化践惑。所以腹泌,MVVM 模式有些時候又被稱作:model-view-binder 模式嘶卧。
具體在 iOS 中,可以使用 KVO 或 Notification 技術達到這種效果凉袱。
在使用中芥吟,我發(fā)現(xiàn)大家對于 MVVM 以及 MVVM 衍生出來的框架(比如 ReactiveCocoa)有一種「敬畏」感。這種「敬畏」感某種程度上就像對神一樣专甩,這主要表現(xiàn)在我沒有聽到大家對于 MVVM 的任何批評钟鸵。
我感覺原因首先是 MVVM 并沒有很大程度上普及,大家對于新技術一般都不熟涤躲,進而不敢妄加評論棺耍。另外,ReactiveCocoa 本身上手的復雜性种樱,也讓很多人感覺到這種技術很高深難懂蒙袍,進而加重了大家對它的「敬畏」。
MVVM 的作用和問題
MVVM 在實際使用中嫩挤,確實能夠使得 Model 層和 View 層解耦害幅,但是如果你需要實現(xiàn) MVVM 中的雙向綁定的話,那么通常就需要引入更多復雜的框架來實現(xiàn)了岂昭。
對此以现,MVVM 的作者 John Gossman 的 批評 應該是最為中肯的。John Gossman 對 MVVM 的批評主要有兩點:
第一點:數據綁定使得 Bug 很難被調試。你看到界面異常了邑遏,有可能是你 View 的代碼有 Bug佣赖,也可能是 Model 的代碼有問題。數據綁定使得一個位置的 Bug 被快速傳遞到別的位置无宿,要定位原始出問題的地方就變得不那么容易了茵汰。
第二點:對于過大的項目,數據綁定需要花費更多的內存孽鸡。
某種意義上來說蹂午,我認為就是數據綁定使得 MVVM 變得復雜和難用了。但是彬碱,這個缺點同時也被很多人認為是優(yōu)點豆胸。
ReactiveCocoa
函數式編程(Functional Programming)和響應式編程(React Programming)也是當前很火的兩個概念,它們的結合可以很方便地實現(xiàn)數據的綁定巷疼。于是晚胡,在 iOS 編程中,ReactiveCocoa 橫空出世了嚼沿,它的概念都非常 新估盘,包括:
函數式編程(Functional Programming),函數也變成一等公民了骡尽,可以擁有和對象同樣的功能遣妥,例如當成參數傳遞,當作返回值等攀细◇锊龋看看 Swift 語言帶來的眾多函數式編程的特性,就你知道這多 Cool 了谭贪。
響應式編程(React Programming)境钟,原來我們基于事件(Event)的處理方式都弱了,現(xiàn)在是基于輸入(在 ReactiveCocoa 里叫 Signal)的處理方式俭识。輸入還可以通過函數式編程進行各種 Combine 或 Filter慨削,盡顯各種靈活的處理。
無狀態(tài)(Stateless)套媚,狀態(tài)是函數的魔鬼缚态,無狀態(tài)使得函數能更好地測試。
不可修改(Immutable)凑阶,數據都是不可修改的猿规,使得軟件邏輯簡單,也可以更好地測試宙橱。
哇姨俩,所有這些都太 Cool 了蘸拔。當我看到的時候,我都雞凍了环葵!
我們應該客觀評價 MVVM 和 ReactiveCocoa
但是但是调窍,我突然想到,我好象只需要一個 ViewModel 而已张遭,我完全可以簡單地做一個 ViewModel 的工廠類或 Service 類就可以了邓萨,為什么要引入這么多框架?現(xiàn)有的 MVC 真的有那么大的問題嗎菊卷?
直到現(xiàn)在缔恳,ReactiveCocoa 在國內外還都是在小眾領域,沒有被大量接受成為主流的編程框架洁闰。不只是在 iOS 語言歉甚,在別的語言中,例如 Java 中的 RxJava 也同樣沒有成為主流扑眉。
我在這里纸泄,不是想說 ReactiveCocoa 不好,也不是想說 MVVM 不好腰素,而是想讓大家都能夠有一個客觀的認識聘裁。ReactiveCocoa 和 MVVM 不應該被神化,它是一種新穎的編程框架弓千,能夠解決舊有編程框架的一些問題衡便,但是也會帶來一些新問題,僅此而已计呈。如果不能使好的駕馭 ReactiveCocoa砰诵,同樣會造成 Controller 代碼過于復雜征唬,代碼邏輯不易維護的問題捌显。
總結
有一些人總是追趕著技術,有什么新技術不管三七二十一立馬就用总寒,結果被各種坑扶歪。
又有一些人,總是擔心新技術帶來的技術風險摄闸,不愿意學習善镰。結果現(xiàn)在還有人在用 MRC 手動管理引用計數。
而我想說年枕,我們需要保持的是一個擁抱變化的心炫欺,以及理性分析的態(tài)度。在新技術的面前熏兄,不盲從品洛,也不守舊树姨,一切的決策都應該建立在認真分析的基礎上,這樣才能應對技術的變化桥状。
(感謝T大神的分享)