iOS-MVC

本文,包含了關(guān)于MVC的如下內(nèi)容:

一、MVC介紹
二、MVC的改進(jìn)
三、MVVM簡(jiǎn)評(píng)

一、MVC的介紹

MVC,全稱是 Model View Controller,是模型 (model)-視圖 (view)-控制器 (controller) 的縮寫。它表示的是一種常見(jiàn)的客戶端軟件開(kāi)發(fā)框架。

一個(gè)UI交互的整體過(guò)程:

  • View接受用戶操作發(fā)送給Controller,Controller根據(jù)操作對(duì)數(shù)據(jù)進(jìn)行修改;
  • Controller接受數(shù)據(jù)修改的通知,并根據(jù)通知更新對(duì)應(yīng)的UI。

當(dāng)然Controller可能有一些自有邏輯會(huì)修改數(shù)據(jù)或者更新UI,從屬關(guān)系上來(lái)說(shuō)View和Model都屬于Controller。

模塊間的通信

1. Controller->View和Controller->Model

對(duì)Model和View的訪問(wèn)是一樣的,在Controller的代碼中,可以直接持有它們的類或者實(shí)例,進(jìn)行方法和屬性的訪問(wèn)。

2. View->Controller
  1. target:action:用戶在view上觸發(fā)事件,view會(huì)產(chǎn)生一個(gè)action動(dòng)作,在controller中,通過(guò)addtarget:方法,接受action動(dòng)作。controller自身設(shè)置target,view在需要通知controller時(shí)向controller發(fā)送action。
  2. delegate: controller作為view的被委托者,代理。分為:
  • 動(dòng)作類delegate:在controller中響應(yīng)action動(dòng)作。
  • data source數(shù)據(jù)源類delegate:為view 提供需要顯示的數(shù)據(jù)。
3. Model->Controller

如果Model希望自己的變化被Controller感知呢怎么辦呢?iOS SDK提供了兩種方法:

  1. Notification: model自己設(shè)置一個(gè)通知中心NSNotificationCenter,需要知道m(xù)odel數(shù)據(jù)變化的的controller自己注冊(cè)一個(gè)通知addObserver來(lái)監(jiān)聽(tīng)model的數(shù)據(jù)數(shù)據(jù)變化,讓不需要監(jiān)聽(tīng)model需要移除注冊(cè)監(jiān)聽(tīng)
  2. KVO: 使用addObserver:forKeyPath:option:context:方法和removeObserver:forKeyPath:方法來(lái)對(duì)model設(shè)置和移除注冊(cè)監(jiān)聽(tīng)

MVC優(yōu)點(diǎn):

(一)、低耦合性
  視圖層和業(yè)務(wù)層分離,這樣就允許更改視圖層代碼而不用重新編譯模型和控制器代碼,同樣,一個(gè)應(yīng)用的業(yè)務(wù)流程或者業(yè)務(wù)規(guī)則的改變只需要改動(dòng)MVC的模型層即可。因?yàn)槟P团c控制器和視圖相分離,所以很容易改變應(yīng)用程序的數(shù)據(jù)層和業(yè)務(wù)規(guī)則。
(二)、高重用性和可適用性
  視圖層和model可復(fù)用。
(三)、學(xué)習(xí)成本較低,減少代碼量,比較容易維護(hù)。

然而在日常開(kāi)發(fā)中,我們?cè)谠O(shè)置View的時(shí)候會(huì)直接來(lái)調(diào)用Model,所以事實(shí)上典型的MVC的原則已經(jīng)違背了,但是這種情況是一直發(fā)生的甚至于人們不覺(jué)得這里有哪些不對(duì)。如果嚴(yán)格遵守MVC的話,你會(huì)把對(duì)view的設(shè)置放在Controller中,不向View傳遞一個(gè)Model對(duì)象,這樣就會(huì)大大增加Controller的體積。

缺點(diǎn):

厚重的ViewController
遺失的網(wǎng)絡(luò)邏輯(無(wú)立足之地)
較差的可測(cè)試性

那到底我們應(yīng)不應(yīng)該用MVC呢?請(qǐng)繼續(xù)往下看

二、MVC如何改進(jìn)

使用MVC避免不了會(huì)造成Controller過(guò)于龐大臃腫,首先對(duì)Controller進(jìn)行瘦身。

1. 對(duì)Controller瘦身

Controller 里面就只應(yīng)該存放不能復(fù)用的代碼,這些代碼包括:

  • 在初始化時(shí),構(gòu)造相應(yīng)的 View 和 Model。
  • 監(jiān)聽(tīng) Model 層的事件,將 Model 層的數(shù)據(jù)傳遞到 View 層。
  • 監(jiān)聽(tīng) View 層的事件,并且將 View 層的事件轉(zhuǎn)發(fā)到 Model 層。

如果 Controller 只有以上的這些代碼,那么它的邏輯將非常簡(jiǎn)單,而且也會(huì)非常短。
但是,我們卻很難做到這一點(diǎn),因?yàn)檫€是有很多邏輯我們不知道寫在哪里,于是就都寫到了 Controller 中了,那我們接下來(lái)就看看其它邏輯應(yīng)該寫在哪里。

objc.io 是一個(gè)非常有名的 iOS 開(kāi)發(fā)博客,它上面的第一課 《Lighter View Controllers》 上就講了很多這樣的技巧,我們先總結(jié)一下它里面的觀點(diǎn):

  • 將 UITableView 的 Data Source 分離到另外一個(gè)類中。
  • 將數(shù)據(jù)獲取和轉(zhuǎn)換的邏輯分別到另外一個(gè)類中。
  • 將拼裝控件的邏輯,分離到另外一個(gè)類中。

你想明白了嗎?其實(shí) MVC 雖然只有三層,但是它并沒(méi)有限制你只能有三層。所以,我們可以將 Controller 里面過(guò)于臃腫的邏輯抽取出來(lái),形成新的可復(fù)用模塊或架構(gòu)層次。
我個(gè)人對(duì)于邏輯的抽取,有以下總結(jié)。

1.1將網(wǎng)絡(luò)請(qǐng)求抽象到單獨(dú)的類中

這部分代碼從 Controller 中剝離出來(lái)后,不但簡(jiǎn)化了 Controller 中的邏輯,也達(dá)到了網(wǎng)絡(luò)層的代碼復(fù)用的效果。

1.2將界面的拼裝抽象到專門的類中

新手寫代碼,喜歡在 Controller 中把一個(gè)個(gè) UILabel ,UIButton,UITextField 往 self.view 上用 addSubView 方法放。我建議大家可以用兩種辦法把這些代碼從 Controller 中剝離。
方法一:構(gòu)造專門的 UIView 的子類,來(lái)負(fù)責(zé)這些控件的拼裝。這是最徹底和優(yōu)雅的方式,不過(guò)稍微麻煩一些的是,你需要把這些控件的事件回調(diào)先接管,再都一一暴露回 Controller。
方法二:用一個(gè)靜態(tài)的 Util 類,幫助你做 UIView 的拼裝工作。這種方式稍微做得不太徹底,但是比較簡(jiǎn)單。
對(duì)于一些能復(fù)用的 UI 控件,我建議用方法一。如果項(xiàng)目工程比較復(fù)雜,我也建議用方法一。如果項(xiàng)目太緊,另外相關(guān)項(xiàng)目的代碼量也不多,可以嘗試方法二。

1.3構(gòu)造 ViewModel

誰(shuí)說(shuō) MVC 就不能用 ViewModel 的?MVVM 的優(yōu)點(diǎn)我們一樣可以借鑒。具體做法就是將 ViewController 給 View 傳遞數(shù)據(jù)這個(gè)過(guò)程,抽象成構(gòu)造 ViewModel 的過(guò)程
這樣抽象之后,View 只接受 ViewModel,而 Controller 只需要傳遞 ViewModel 這么一行代碼。而另外構(gòu)造 ViewModel 的過(guò)程,我們就可以移動(dòng)到另外的類中了。
在具體實(shí)踐中,我建議大家專門創(chuàng)建構(gòu)造 ViewModel 工廠類,參見(jiàn) 工廠模式。另外,也可以專門將數(shù)據(jù)存取都抽將到一個(gè) Service 層,由這層來(lái)提供 ViewModel 的獲取。
這樣一來(lái),Controller設(shè)置view內(nèi)容的代碼不僅剝離出去了,而且也將model從view中脫離出來(lái)了

1.4專門構(gòu)造存儲(chǔ)類

剛剛說(shuō)到 ViewModel 的構(gòu)造可以抽獎(jiǎng)到一個(gè) Service 層。與此相應(yīng)的,數(shù)據(jù)的存儲(chǔ)也應(yīng)該由專門的對(duì)象來(lái)做。數(shù)據(jù)存取放在專門的類中,就可以針對(duì)存取做額外的事情了。比如:對(duì)一些熱點(diǎn)數(shù)據(jù)增加緩存、處理數(shù)據(jù)遷移相關(guān)的邏輯。
如果要做得更細(xì),可以把存儲(chǔ)引擎再抽象出一層。這樣你就可以方便地切換存儲(chǔ)的底層,例如從 sqlite 切換到 key-value 的存儲(chǔ)引擎等。

小結(jié)

通過(guò)代碼的抽取,我們可以將原本的 MVC 設(shè)計(jì)模式中的 ViewController 進(jìn)一步拆分,構(gòu)造出 網(wǎng)絡(luò)請(qǐng)求層、ViewModel 層、Service 層、Storage 層等其它類,來(lái)配合 Controller 工作,從而使 Controller 更加簡(jiǎn)單,我們的 App 更容易維護(hù)。

三、 MVVM

MVVM 的歷史

MVVM 是 Model-View-ViewModel 的簡(jiǎn)寫。
相對(duì)于 MVC 的歷史來(lái)說(shuō),MVVM 是一個(gè)相當(dāng)新的架構(gòu),MVVM 最早于 2005 年被微軟的 WPF 和 Silverlight 的架構(gòu)師 John Gossman 提出,并且應(yīng)用在微軟的軟件開(kāi)發(fā)中。當(dāng)時(shí) MVC 已經(jīng)被提出了 20 多年了,可見(jiàn)兩者出現(xiàn)的年代差別有多大。
MVVM 在使用當(dāng)中,通常還會(huì)利用雙向綁定技術(shù),使得 Model 變化時(shí),ViewModel 會(huì)自動(dòng)更新,而 ViewModel 變化時(shí),View 也會(huì)自動(dòng)變化。所以,MVVM 模式有些時(shí)候又被稱作:model-view-binder 模式。
具體在 iOS 中,可以使用 KVO 或 Notification 技術(shù)達(dá)到這種效果。

MVVM 的神化

在使用中,我發(fā)現(xiàn)大家對(duì)于 MVVM 以及 MVVM 衍生出來(lái)的框架(比如 ReactiveCocoa)有一種「敬畏」感。這種「敬畏」感某種程度上就像對(duì)神一樣,這主要表現(xiàn)在我沒(méi)有聽(tīng)到大家對(duì)于 MVVM 的任何批評(píng)。
我感覺(jué)原因首先是 MVVM 并沒(méi)有很大程度上普及,大家對(duì)于新技術(shù)一般都不熟,進(jìn)而不敢妄加評(píng)論。另外,ReactiveCocoa 本身上手的復(fù)雜性,也讓很多人感覺(jué)到這種技術(shù)很高深難懂,進(jìn)而加重了大家對(duì)它的「敬畏」。

MVVM 的作用和問(wèn)題

MVVM 在實(shí)際使用中,確實(shí)能夠使得 Model 層和 View 層解耦,但是如果你需要實(shí)現(xiàn) MVVM 中的雙向綁定的話,那么通常就需要引入更多復(fù)雜的框架來(lái)實(shí)現(xiàn)了。
對(duì)此,MVVM 的作者 John Gossman 的 批評(píng) 應(yīng)該是最為中肯的。John Gossman 對(duì) MVVM 的批評(píng)主要有兩點(diǎn):
第一點(diǎn):數(shù)據(jù)綁定使得 Bug 很難被調(diào)試。你看到界面異常了,有可能是你 View 的代碼有 Bug,也可能是 Model 的代碼有問(wèn)題。數(shù)據(jù)綁定使得一個(gè)位置的 Bug 被快速傳遞到別的位置,要定位原始出問(wèn)題的地方就變得不那么容易了。
第二點(diǎn):對(duì)于過(guò)大的項(xiàng)目,數(shù)據(jù)綁定需要花費(fèi)更多的內(nèi)存。
某種意義上來(lái)說(shuō),我認(rèn)為就是數(shù)據(jù)綁定使得 MVVM 變得復(fù)雜和難用了。但是,這個(gè)缺點(diǎn)同時(shí)也被很多人認(rèn)為是優(yōu)點(diǎn)。

ReactiveCocoa

函數(shù)式編程(Functional Programming)和響應(yīng)式編程(React Programming)也是當(dāng)前很火的兩個(gè)概念,它們的結(jié)合可以很方便地實(shí)現(xiàn)數(shù)據(jù)的綁定。于是,在 iOS 編程中,ReactiveCocoa 橫空出世了,它的概念都非常 新,包括:
函數(shù)式編程(Functional Programming),函數(shù)也變成一等公民了,可以擁有和對(duì)象同樣的功能,例如當(dāng)成參數(shù)傳遞,當(dāng)作返回值等。看看 Swift 語(yǔ)言帶來(lái)的眾多函數(shù)式編程的特性,就你知道這多 Cool 了。
響應(yīng)式編程(React Programming),原來(lái)我們基于事件(Event)的處理方式都弱了,現(xiàn)在是基于輸入(在 ReactiveCocoa 里叫 Signal)的處理方式。輸入還可以通過(guò)函數(shù)式編程進(jìn)行各種 Combine 或 Filter,盡顯各種靈活的處理。
無(wú)狀態(tài)(Stateless),狀態(tài)是函數(shù)的魔鬼,無(wú)狀態(tài)使得函數(shù)能更好地測(cè)試。
不可修改(Immutable),數(shù)據(jù)都是不可修改的,使得軟件邏輯簡(jiǎn)單,也可以更好地測(cè)試。

哇,所有這些都太 Cool 了。當(dāng)我看到的時(shí)候,我都雞凍了!

我們應(yīng)該客觀評(píng)價(jià) MVVM 和 ReactiveCocoa

但是但是,我突然想到,我好象只需要一個(gè) ViewModel 而已,我完全可以簡(jiǎn)單地做一個(gè) ViewModel 的工廠類或 Service 類就可以了,為什么要引入這么多框架?現(xiàn)有的 MVC 真的有那么大的問(wèn)題嗎?
直到現(xiàn)在,ReactiveCocoa 在國(guó)內(nèi)外還都是在小眾領(lǐng)域,沒(méi)有被大量接受成為主流的編程框架。不只是在 iOS 語(yǔ)言,在別的語(yǔ)言中,例如 Java 中的 RxJava 也同樣沒(méi)有成為主流。
我在這里,不是想說(shuō) ReactiveCocoa 不好,也不是想說(shuō) MVVM 不好,而是想讓大家都能夠有一個(gè)客觀的認(rèn)識(shí)。ReactiveCocoa 和 MVVM 不應(yīng)該被神化,它是一種新穎的編程框架,能夠解決舊有編程框架的一些問(wèn)題,但是也會(huì)帶來(lái)一些新問(wèn)題,僅此而已。如果不能使好的駕馭 ReactiveCocoa,同樣會(huì)造成 Controller 代碼過(guò)于復(fù)雜,代碼邏輯不易維護(hù)的問(wèn)題。

瘦身部分參考了:被誤解的 MVC 和被神化的 MVVM

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容