轉(zhuǎn)載 :http://casatwy.com/iosying-yong-jia-gou-tan-kai-pian.html
OS客戶端應(yīng)用架構(gòu)看似簡(jiǎn)單,但實(shí)際上要考慮的事情不少。本文作者將以系列文章的形式來回答iOS應(yīng)用架構(gòu)中的種種問題,本文是其中的第二篇,主要講View層的組織和調(diào)用方案。中篇主要討論MVC、MVCS、MVVM、VIPER等架構(gòu)在iOS開發(fā)中的應(yīng)用。
關(guān)于MVC、MVVM等一大堆思想
其實(shí)這些都是相對(duì)通用的思想,萬變不離其宗的還是在開篇里面我提到的那三個(gè)角色:數(shù)據(jù)管理者,數(shù)據(jù)加工者,數(shù)據(jù)展示者。這些五花八門的思想,不外乎就是制訂了一個(gè)規(guī)范,規(guī)定了這三個(gè)角色應(yīng)當(dāng)如何進(jìn)行數(shù)據(jù)交換。但同時(shí)這些也是爭(zhēng)議最多的話題,所以我在這里來把幾個(gè)主流思想做一個(gè)梳理,當(dāng)你在做View層架構(gòu)時(shí),能夠有個(gè)比較好的參考。
MVC
MVC(Model-View-Controller)是最老牌的的思想,老牌到4人幫的書里把它歸成了一種模式,其中Model就是作為數(shù)據(jù)管理者,View作為數(shù)據(jù)展示者,Controller作為數(shù)據(jù)加工者,Model和View又都是由Controller來根據(jù)業(yè)務(wù)需求調(diào)配,所以Controller還負(fù)擔(dān)了一個(gè)數(shù)據(jù)流調(diào)配的功能。正在我寫這篇文章的時(shí)候,我看到InfoQ發(fā)了這篇文章,里面提到了一個(gè)移動(dòng)開發(fā)中的痛點(diǎn)是:對(duì)MVC架構(gòu)劃分的理解。我當(dāng)時(shí)沒能夠去參加這個(gè)座談會(huì),也沒辦法發(fā)表個(gè)人意見,所以就只能在這里寫寫了。
在iOS開發(fā)領(lǐng)域,我們應(yīng)當(dāng)如何進(jìn)行MVC的劃分?
這里面其實(shí)有兩個(gè)問題:
為什么我們會(huì)糾結(jié)于iOS開發(fā)領(lǐng)域中MVC的劃分問題?
在iOS開發(fā)領(lǐng)域中,怎樣才算是劃分的正確姿勢(shì)?
為什么我們會(huì)糾結(jié)于iOS開發(fā)領(lǐng)域中MVC的劃分問題?
關(guān)于這個(gè),每個(gè)人糾結(jié)的點(diǎn)可能不太一樣,我也不知道當(dāng)時(shí)座談會(huì)上大家的觀點(diǎn)。但請(qǐng)?jiān)试S我猜一下:是不是因?yàn)閁IViewController中自帶了一個(gè)View,且控制了View的整個(gè)生命周期(viewDidLoad,viewWillAppear...),而在常識(shí)中我們都知道Controller不應(yīng)該和View有如此緊密的聯(lián)系,所以才導(dǎo)致大家對(duì)劃分產(chǎn)生困惑?,下面我會(huì)針對(duì)這個(gè)猜測(cè)來給出我的意見。
在服務(wù)端開發(fā)領(lǐng)域,Controller和View的交互方式一般都是這樣,比如Yii:
/*
...
數(shù)據(jù)庫取數(shù)據(jù)
...
處理數(shù)據(jù)
...
*/
// 此處$this就是Controller
$this->render("plan",array(
'planList' => $planList,
'plan_id' => $_GET['id'],
));
這里Controller和View之間區(qū)分得非常明顯,Controller做完自己的事情之后,就把所有關(guān)于View的工作交給了頁面渲染引擎去做,Controller不會(huì)去做任何關(guān)于View的事情,包括生成View,這些都由渲染引擎代勞了。這是一個(gè)區(qū)別,但其實(shí)服務(wù)端View的概念和Native應(yīng)用View的概念,真正的區(qū)別在于:從概念上嚴(yán)格劃分的話,服務(wù)端其實(shí)根本沒有View,拜HTTP協(xié)議所賜,我們平時(shí)所討論的View只是用于描述View的字符串(更實(shí)質(zhì)的應(yīng)該稱之為數(shù)據(jù)),真正的View是瀏覽器。。
所以服務(wù)端只管生成對(duì)View的描述,至于對(duì)View的長(zhǎng)相,UI事件監(jiān)聽和處理,都是瀏覽器負(fù)責(zé)生成和維護(hù)的。但是在Native這邊來看,原本屬于瀏覽器的任務(wù)也逃不掉要自己做。那么這件事情由誰來做最合適?蘋果給出的答案是:UIViewController。
鑒于蘋果在這一層做了很多艱苦卓絕的努力,讓iOS工程師們不必親自去實(shí)現(xiàn)這些內(nèi)容。而且,它把所有的功能都放在了UIView上,并且把UIView做成不光可以展示UI,還可以作為容器的一個(gè)對(duì)象。
看到這兒你明白了嗎?UIView的另一個(gè)身份其實(shí)是容器!UIViewController中自帶的那個(gè)view,它的主要任務(wù)就是作為一個(gè)容器。如果它所有的相關(guān)命名都改成ViewContainer,那么代碼就會(huì)變成這樣:
- (void)viewContainerDidLoad
{
[self.viewContainer addSubview:self.label];
[self.viewContainer addSubview:self.tableView];
[self.viewContainer addSubview:self.button];
[self.viewContainer addSubview:self.textField];
}
... ...
僅僅改了個(gè)名字,現(xiàn)在是不是感覺清晰了很多?如果再要說詳細(xì)一點(diǎn),我們平常所認(rèn)為的服務(wù)端MVC是這樣劃分的:

但事實(shí)上,整套流程的MVC劃分是這樣:

由圖中可以看出,我們服務(wù)端開發(fā)在這個(gè)概念下,其實(shí)只涉及M和C的開發(fā)工作,瀏覽器作為View的容器,負(fù)責(zé)View的展示和事件的監(jiān)聽。那么對(duì)應(yīng)到iOS客戶端的MVC劃分上面來,就是這樣:

唯一區(qū)別在于,View的容器在服務(wù)端,是由Browser負(fù)責(zé),在整個(gè)網(wǎng)站的流程中,這個(gè)容器放在Browser是非常合理的。在iOS客戶端,View的容器是由UIViewController中的view負(fù)責(zé),我也覺得蘋果做的這個(gè)選擇是非常正確明智的。
因?yàn)闉g覽器和服務(wù)端之間的關(guān)系非常松散,而且他們分屬于兩個(gè)不同陣營(yíng),服務(wù)端將對(duì)View的描述生成之后,交給瀏覽器去負(fù)責(zé)展示,然而一旦view上有什么事件產(chǎn)生,基本上是很少傳遞到服務(wù)器(也就是所謂的Controller)的(要傳也可以:AJAX),都是在瀏覽器這邊把事情都做掉,所以在這種情況下,View容器就適合放在瀏覽器(V)這邊。
但是在iOS開發(fā)領(lǐng)域,雖然也有讓View去監(jiān)聽事件的做法,但這種做法非常少,都是把事件回傳給Controller,然后Controller再另行調(diào)度。所以這時(shí)候,View的容器放在Controller就非常合適。Controller可以因?yàn)椴煌录漠a(chǎn)生去很方便地更改容器內(nèi)容,比如加載失敗時(shí),把容器內(nèi)容換成失敗頁面的View,無網(wǎng)絡(luò)時(shí),把容器頁面換成無網(wǎng)絡(luò)的View等等。
在iOS開發(fā)領(lǐng)域中,怎樣才算是MVC劃分的正確姿勢(shì)?
這個(gè)問題其實(shí)在上面已經(jīng)解答掉一部分了,那么這個(gè)問題的答案就當(dāng)是對(duì)上面問題的一個(gè)總結(jié)吧。
M應(yīng)該做的事:
給ViewController提供數(shù)據(jù)
給ViewController存儲(chǔ)數(shù)據(jù)提供接口
提供經(jīng)過抽象的業(yè)務(wù)基本組件,供Controller調(diào)度
C應(yīng)該做的事:
管理View Container的生命周期
負(fù)責(zé)生成所有的View實(shí)例,并放入View Container
監(jiān)聽來自View與業(yè)務(wù)有關(guān)的事件,通過與Model的合作,來完成對(duì)應(yīng)事件的業(yè)務(wù)。
V應(yīng)該做的事:
響應(yīng)與業(yè)務(wù)無關(guān)的事件,并因此引發(fā)動(dòng)畫效果,點(diǎn)擊反饋(如果合適的話,盡量還是放在View去做)等。
界面元素表達(dá)
我通過與服務(wù)端MVC劃分的對(duì)比來回答了這兩個(gè)問題,之所以這么做,是因?yàn)槲抑烙泻芏鄆OS工程師之前是從服務(wù)端轉(zhuǎn)過來的。我也是這樣,在進(jìn)安居客之前,我也是做服務(wù)端開發(fā)的,在學(xué)習(xí)iOS的過程中,我也曾經(jīng)對(duì)iOS領(lǐng)域的MVC劃分問題產(chǎn)生過疑惑,我疑惑的點(diǎn)就是前面開篇我猜測(cè)的點(diǎn)。如果有人問我iOS中應(yīng)該怎么做MVC的劃分,我就會(huì)像上面這么回答。
MVCS
蘋果自身就采用的是這種架構(gòu)思路,從名字也能看出,也是基于MVC衍生出來的一套架構(gòu)。從概念上來說,它拆分的部分是Model部分,拆出來一個(gè)Store。這個(gè)Store專門負(fù)責(zé)數(shù)據(jù)存取。但從實(shí)際操作的角度上講,它拆開的是Controller。
這算是瘦Model的一種方案,瘦Model只是專門用于表達(dá)數(shù)據(jù),然后存儲(chǔ)、數(shù)據(jù)處理都交給外面的來做。MVCS使用的前提是,它假設(shè)了你是瘦Model,同時(shí)數(shù)據(jù)的存儲(chǔ)和處理都在Controller去做。所以對(duì)應(yīng)到MVCS,它在一開始就是拆分的Controller。因?yàn)镃ontroller做了數(shù)據(jù)存儲(chǔ)的事情,就會(huì)變得非常龐大,那么就把Controller專門負(fù)責(zé)存取數(shù)據(jù)的那部分抽離出來,交給另一個(gè)對(duì)象去做,這個(gè)對(duì)象就是Store。這么調(diào)整之后,整個(gè)結(jié)構(gòu)也就變成了真正意義上的MVCS。
關(guān)于胖Model和瘦Model
我在面試和跟別人聊天時(shí),發(fā)現(xiàn)知道胖Model和瘦Model的概念的人不是很多。大約兩三年前國(guó)外業(yè)界曾經(jīng)對(duì)此有過非常激烈的討論,主題就是Fat model, skinny controller。現(xiàn)在關(guān)于這方面的討論已經(jīng)不多了,然而直到今天胖Model和瘦Model哪個(gè)更好,業(yè)界也還沒有定論,所以這算是目前業(yè)界懸而未解的一個(gè)爭(zhēng)議。我很少看到國(guó)內(nèi)有討論這個(gè)的資料,所以在這里我打算補(bǔ)充一下什么叫胖Model什么叫瘦Model。以及他們的爭(zhēng)論來源于何處。
什么叫胖Model?
胖Model包含了部分弱業(yè)務(wù)邏輯。胖Model要達(dá)到的目的是,Controller從胖Model這里拿到數(shù)據(jù)之后,不用額外做操作或者只要做非常少的操作,就能夠?qū)?shù)據(jù)直接應(yīng)用在View上。舉個(gè)例子:
Raw Data:
timestamp:1234567
FatModel:
@property (nonatomic, assign) CGFloat timestamp;
- (NSString *)ymdDateString; // 2015-04-20 15:16
- (NSString *)gapString; // 3分鐘前、1小時(shí)前、一天前、2015-3-13 12:34
Controller:
self.dateLabel.text = [FatModel ymdDateString];
self.gapLabel.text = [FatModel gapString];
把timestamp轉(zhuǎn)換成具體業(yè)務(wù)上所需要的字符串,這屬于業(yè)務(wù)代碼,算是弱業(yè)務(wù)。FatModel做了這些弱業(yè)務(wù)之后,Controller就能變得非常skinny,Controller只需要關(guān)注強(qiáng)業(yè)務(wù)代碼就行了。眾所周知,強(qiáng)業(yè)務(wù)變動(dòng)的可能性要比弱業(yè)務(wù)大得多,弱業(yè)務(wù)相對(duì)穩(wěn)定,所以弱業(yè)務(wù)塞進(jìn)Model里面是沒問題的。另一方面,弱業(yè)務(wù)重復(fù)出現(xiàn)的頻率要大于強(qiáng)業(yè)務(wù),對(duì)復(fù)用性的要求更高,如果這部分業(yè)務(wù)寫在Controller,類似的代碼會(huì)灑得到處都是,一旦弱業(yè)務(wù)有修改(弱業(yè)務(wù)修改頻率低不代表就沒有修改),這個(gè)事情就是一個(gè)災(zāi)難。如果塞到Model里面去,改一處很多地方就能跟著改,就能避免這場(chǎng)災(zāi)難。
然而其缺點(diǎn)就在于,胖Model相對(duì)比較難移植,雖然只是包含弱業(yè)務(wù),但好歹也是業(yè)務(wù),遷移的時(shí)候很容易拔出蘿卜帶出泥。另外一點(diǎn),MVC的架構(gòu)思想更加傾向于Model是一個(gè)Layer,而不是一個(gè)Object,不應(yīng)該把一個(gè)Layer應(yīng)該做的事情交給一個(gè)Object去做。最后一點(diǎn),軟件是會(huì)成長(zhǎng)的,F(xiàn)atModel很有可能隨著軟件的成長(zhǎng)越來越Fat,最終難以維護(hù)。
什么叫瘦Model?
瘦Model只負(fù)責(zé)業(yè)務(wù)數(shù)據(jù)的表達(dá),所有業(yè)務(wù)無論強(qiáng)弱一律扔到Controller。瘦Model要達(dá)到的目的是,盡一切可能去編寫細(xì)粒度Model,然后配套各種helper類或方法來對(duì)弱業(yè)務(wù)做抽象,強(qiáng)業(yè)務(wù)依舊交給Controller。舉個(gè)例子:
Raw Data:
{
"name":"casa",
"sex":"male",
}
SlimModel:
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *sex;
Helper:
#define Male 1;
#define Female 0;
+ (BOOL)sexWithString:(NSString *)sex;
Controller:
if ([Helper sexWithString:SlimModel.sex] == Male) {
...
}
由于SlimModel跟業(yè)務(wù)完全無關(guān),它的數(shù)據(jù)可以交給任何一個(gè)能處理它數(shù)據(jù)的Helper或其他的對(duì)象,來完成業(yè)務(wù)。在代碼遷移的時(shí)候獨(dú)立性很強(qiáng),很少會(huì)出現(xiàn)拔出蘿卜帶出泥的情況。另外,由于SlimModel只是數(shù)據(jù)表達(dá),對(duì)它進(jìn)行維護(hù)基本上是0成本,軟件膨脹得再厲害,SlimModel也不會(huì)大到哪兒去。
缺點(diǎn)就在于,Helper這種做法也不見得很好,這里有一篇文章批判了這個(gè)事情。另外,由于Model的操作會(huì)出現(xiàn)在各種地方,SlimModel在一定程度上違背了DRY(Don't Repeat Yourself)的思路,Controller仍然不可避免在一定程度上出現(xiàn)代碼膨脹。
我的態(tài)度?嗯,我會(huì)在本門心法這一節(jié)里面說。
說回來,MVCS是基于瘦Model的一種架構(gòu)思路,把原本Model要做的很多事情中的其中一部分關(guān)于數(shù)據(jù)存儲(chǔ)的代碼抽象成了Store,在一定程度上降低了Controller的壓力。
MVVM
MVVM去年在業(yè)界討論得非常多,無論國(guó)內(nèi)還是國(guó)外都討論得非常熱烈,尤其是在ReactiveCocoa這個(gè)庫成熟之后,ViewModel和View的信號(hào)機(jī)制在iOS下終于有了一個(gè)相對(duì)優(yōu)雅的實(shí)現(xiàn)。MVVM本質(zhì)上也是從MVC中派生出來的思想,MVVM著重想要解決的問題是盡可能地減少Controller的任務(wù)。不管MVVM也好,MVCS也好,他們的共識(shí)都是Controller會(huì)隨著軟件的成長(zhǎng),變很大很難維護(hù)很難測(cè)試。只不過兩種架構(gòu)思路的前提不同,MVCS是認(rèn)為Controller做了一部分Model的事情,要把它拆出來變成Store,MVVM是認(rèn)為Controller做了太多數(shù)據(jù)加工的事情,所以MVVM把數(shù)據(jù)加工的任務(wù)從Controller中解放了出來,使得Controller只需要專注于數(shù)據(jù)調(diào)配的工作,ViewModel則去負(fù)責(zé)數(shù)據(jù)加工并通過通知機(jī)制讓View響應(yīng)ViewModel的改變。
MVVM是基于胖Model的架構(gòu)思路建立的,然后在胖Model中拆出兩部分:Model和ViewModel。關(guān)于這個(gè)觀點(diǎn)我要做一個(gè)額外解釋:胖Model做的事情是先為Controller減負(fù),然后由于Model變胖,再在此基礎(chǔ)上拆出ViewModel,跟業(yè)界普遍認(rèn)知的MVVM本質(zhì)上是為Controller減負(fù)這個(gè)說法并不矛盾,因?yàn)榕諱odel做的事情也是為Controller減負(fù)。
另外,我前面說MVVM把數(shù)據(jù)加工的任務(wù)從Controller中解放出來,跟MVVM拆分的是胖Model也不矛盾。要做到解放Controller,首先你得有個(gè)胖Model,然后再把這個(gè)胖Model拆成Model和ViewModel。
那么MVVM究竟應(yīng)該如何實(shí)現(xiàn)?
這很有可能是大多數(shù)人糾結(jié)的問題,我打算憑我的個(gè)人經(jīng)驗(yàn)試圖在這里回答這個(gè)問題,歡迎交流。
在iOS領(lǐng)域大部分MVVM架構(gòu)都會(huì)使用ReactiveCocoa,但是使用ReactiveCocoa的iOS應(yīng)用就是基于MVVM架構(gòu)的嗎?那當(dāng)然不是,我覺得很多人都存在這個(gè)誤區(qū),我面試過的一些人提到了ReactiveCocoa也提到了MVVM,但他們對(duì)此的理解膚淺得讓我忍俊不禁。嗯,在網(wǎng)絡(luò)層架構(gòu)我會(huì)舉出不使用ReactiveCocoa的例子,現(xiàn)在舉我感覺有點(diǎn)兒早。
MVVM的關(guān)鍵是要有View Model!而不是ReactiveCocoa
注:MVVM要有ViewModel,以及ReactiveCocoa帶來的信號(hào)通知效果,在ReactiveCocoa里就是RAC等相關(guān)宏來實(shí)現(xiàn)。另外,使用ReactiveCocoa能夠比較優(yōu)雅地實(shí)現(xiàn)MVVM模式,就是因?yàn)橛蠷AC等相關(guān)宏的存在。就像它的名字一樣Reactive-響應(yīng)式,這也是區(qū)分MVVM的VM和MVC的C和MVP的P的一個(gè)重要方面。
ViewModel做什么事情?就是把RawData變成直接能被View使用的對(duì)象的一種Model。舉個(gè)例子:
Raw Data:
{
(
(123, 456),
(234, 567),
(345, 678)
)
}
這里的RawData我們假設(shè)是經(jīng)緯度,數(shù)字我隨便寫的不要太在意。然后你有一個(gè)模塊是地圖模塊,把經(jīng)緯度數(shù)組全部都轉(zhuǎn)變成MKAnnotation或其派生類對(duì)于Controller來說是弱業(yè)務(wù),(記住,胖Model就是用來做弱業(yè)務(wù)的),因此我們用ViewModel直接把它轉(zhuǎn)變成MKAnnotation的NSArray,交給Controller之后Controller直接就可以用了。
嗯,這就是ViewModel要做的事情,是不是覺得很簡(jiǎn)單,看不出優(yōu)越性?
安居客Pad應(yīng)用也有一個(gè)地圖模塊,在這里我設(shè)計(jì)了一個(gè)對(duì)象叫做reformer(其實(shí)就是ViewModel),專門用來干這個(gè)事情。那么這么做的優(yōu)越性體現(xiàn)在哪兒呢?
安居客分三大業(yè)務(wù):租房、二手房、新房。這三個(gè)業(yè)務(wù)對(duì)應(yīng)移動(dòng)開發(fā)團(tuán)隊(duì)有三個(gè)API開發(fā)團(tuán)隊(duì),他們各自為政,這就造成了一個(gè)結(jié)果:三個(gè)API團(tuán)隊(duì)回饋給移動(dòng)客戶端的數(shù)據(jù)內(nèi)容雖然一致,但是數(shù)據(jù)格式是不一致的,也就是相同value對(duì)應(yīng)的key是不一致的。但展示地圖的ViewController不可能寫三個(gè),所以肯定少不了要有一個(gè)API數(shù)據(jù)兼容的邏輯,這個(gè)邏輯我就放在reformer里面去做了,于是業(yè)務(wù)流程就變成了這樣:

這么一來,原本復(fù)雜的MKAnnotation組裝邏輯就從Controller里面拆分了出來,Controller可以直接拿著Reformer返回的數(shù)據(jù)進(jìn)行展示。APIManager就屬于Model,reformer就屬于ViewModel。具體關(guān)于reformer的東西我會(huì)放在網(wǎng)絡(luò)層架構(gòu)來詳細(xì)解釋。Reformer此時(shí)扮演的ViewModel角色能夠很好地給Controller減負(fù),同時(shí),維護(hù)成本也大大降低,經(jīng)過reformer產(chǎn)出的永遠(yuǎn)都是MKAnnotation,Controller可以直接拿來使用。
然后另外一點(diǎn),還有一個(gè)業(yè)務(wù)需求是取附近的房源,地圖API請(qǐng)求是能夠hold住這個(gè)需求的,那么其他地方都不用變,在fetchDataWithReformer的時(shí)候換一個(gè)reformer就可以了,其他的事情都交給reformer。
那么ReactiveCocoa應(yīng)該扮演什么角色?
不用ReactiveCocoa也能MVVM,用ReactiveCocoa能更好地體現(xiàn)MVVM的精髓。前面我舉到的例子只是數(shù)據(jù)從API到View的方向,View的操作也會(huì)產(chǎn)生"數(shù)據(jù)",只不過這里的"數(shù)據(jù)"更多的是體現(xiàn)在表達(dá)用戶的操作上,比如輸入了什么內(nèi)容,那么數(shù)據(jù)就是text、選擇了哪個(gè)cell,那么數(shù)據(jù)就是indexPath。那么在數(shù)據(jù)從view走向API或者Controller的方向上,就是ReactiveCocoa發(fā)揮的地方。
我們知道,ViewModel本質(zhì)上算是Model層(因?yàn)槭桥諱odel里面分出來的一部分),所以View并不適合直接持有ViewModel,那么View一旦產(chǎn)生數(shù)據(jù)了怎么辦?扔信號(hào)扔給ViewModel,用誰扔?ReactiveCocoa。
在MVVM中使用ReactiveCocoa的第一個(gè)目的就是如上所說,View并不適合直接持有ViewModel。第二個(gè)目的就在于,ViewModel有可能并不是只服務(wù)于特定的一個(gè)View,使用更加松散的綁定關(guān)系能夠降低ViewModel和View之間的耦合度。
那么在MVVM中,Controller扮演什么角色?
大部分國(guó)內(nèi)外資料闡述MVVM的時(shí)候都是這樣排布的:View <-> ViewModel <-> Model,造成了MVVM不需要Controller的錯(cuò)覺,現(xiàn)在似乎發(fā)展成業(yè)界開始出現(xiàn)MVVM是不需要Controller的。的聲音了。其實(shí)MVVM是一定需要Controller的參與的,雖然MVVM在一定程度上弱化了Controller的存在感,并且給Controller做了減負(fù)瘦身(這也是MVVM的主要目的)。但是,這并不代表MVVM中不需要Controller,MMVC和MVVM他們之間的關(guān)系應(yīng)該是這樣:

(來源:http://www.sprynthesis.com/2014/12/06/reactivecocoa-mvvm-introduction/)
View <-> C <-> ViewModel <-> Model,所以使用MVVM之后,就不需要Controller的說法是不正確的。嚴(yán)格來說MVVM其實(shí)是MVCVM。從圖中可以得知,Controller夾在View和ViewModel之間做的其中一個(gè)主要事情就是將View和ViewModel進(jìn)行綁定。在邏輯上,Controller知道應(yīng)當(dāng)展示哪個(gè)View,Controller也知道應(yīng)當(dāng)使用哪個(gè)ViewModel,然而View和ViewModel它們之間是互相不知道的,所以Controller就負(fù)責(zé)控制他們的綁定關(guān)系,所以叫Controller/控制器就是這個(gè)原因。
前面扯了那么多,其實(shí)歸根結(jié)底就是一句話:在MVC的基礎(chǔ)上,把C拆出一個(gè)ViewModel專門負(fù)責(zé)數(shù)據(jù)處理的事情,就是MVVM。然后,為了讓View和ViewModel之間能夠有比較松散的綁定關(guān)系,于是我們使用ReactiveCocoa,因?yàn)樘O果本身并沒有提供一個(gè)比較適合這種情況的綁定方法。iOS領(lǐng)域里KVO,Notification,block,delegate和target-action都可以用來做數(shù)據(jù)通信,從而來實(shí)現(xiàn)綁定,但都不如ReactiveCocoa提供的RACSignal來的優(yōu)雅,如果不用ReactiveCocoa,綁定關(guān)系可能就做不到那么松散那么好,但并不影響它還是MVVM。
在實(shí)際iOS應(yīng)用架構(gòu)中,MVVM應(yīng)該出現(xiàn)在了大部分創(chuàng)業(yè)公司或者老牌公司新App的iOS應(yīng)用架構(gòu)圖中,據(jù)我所知易寶支付旗下的某個(gè)iOS應(yīng)用就整體采用了MVVM架構(gòu),他們抽出了一個(gè)Action層來裝各種ViewModel,也是屬于相對(duì)合理的結(jié)構(gòu)。
所以Controller在MVVM中,一方面負(fù)責(zé)View和ViewModel之間的綁定,另一方面也負(fù)責(zé)常規(guī)的UI邏輯處理。
VIPER
VIPER(View,Interactor,Presenter,Entity,Routing)。VIPER我并沒有實(shí)際使用過,我是在objc.io上第13期看到的。
但凡出現(xiàn)一個(gè)新架構(gòu)或者我之前并不熟悉的新架構(gòu),有一點(diǎn)我能夠非??隙?,這貨一定又是把MVC的哪個(gè)部分給拆開了(壞笑,做這種判斷的理論依據(jù)在第一篇文章里面我已經(jīng)講過了)。事實(shí)情況是VIPER確實(shí)拆了很多很多,除了View沒拆,其它的都拆了。
我提到的這兩篇文章關(guān)于VIPER都講得很詳細(xì),一看就懂。但具體在使用VIPER的時(shí)候會(huì)有什么坑或者會(huì)有哪些爭(zhēng)議我不是很清楚,硬要寫這一節(jié)的話我只能靠YY,所以我想想還是算了。如果各位讀者有誰在實(shí)際App中采用VIPER架構(gòu)的或者對(duì)VIPER很有興趣的,可以評(píng)論區(qū)里面提出來,我們交流一下。
編后語
為了更好地向讀者輸出更優(yōu)質(zhì)的內(nèi)容,InfoQ將精選來自國(guó)內(nèi)外的優(yōu)秀文章,經(jīng)過整理審校后,發(fā)布到網(wǎng)站。本篇文章作者為田偉宇,原文鏈接為Casa Taloyum。本文已由原作者授權(quán)InfoQ中文站轉(zhuǎn)載。