轉(zhuǎn)自: http://www./cn/articles/ios-app-arch-2-2 iOS客戶端應(yīng)用架構(gòu)看似簡(jiǎn)單,,但實(shí)際上要考慮的事情不少。本文作者將以系列文章的形式來(lái)回答iOS應(yīng)用架構(gòu)中的種種問(wèn)題,,本文是其中的第二篇,,主要講View層的組織和調(diào)用方案。中篇主要討論MVC,、MVCS,、MVVM、VIPER等架構(gòu)在iOS開(kāi)發(fā)中的應(yīng)用,。 關(guān)于MVC,、MVVM等一大堆思想其實(shí)這些都是相對(duì)通用的思想,萬(wàn)變不離其宗的還是在開(kāi)篇里面我提到的那三個(gè)角色:數(shù)據(jù)管理者,,數(shù)據(jù)加工者,,數(shù)據(jù)展示者。這些五花八門的思想,,不外乎就是制訂了一個(gè)規(guī)范,,規(guī)定了這三個(gè)角色應(yīng)當(dāng)如何進(jìn)行數(shù)據(jù)交換。但同時(shí)這些也是爭(zhēng)議最多的話題,,所以我在這里來(lái)把幾個(gè)主流思想做一個(gè)梳理,,當(dāng)你在做View層架構(gòu)時(shí),能夠有個(gè)比較好的參考,。 MVCMVC(Model-View-Controller)是最老牌的的思想,,老牌到4人幫的書(shū)里把它歸成了一種模式,其中Model就是作為數(shù)據(jù)管理者,,View作為數(shù)據(jù)展示者,,Controller作為數(shù)據(jù)加工者,Model和View又都是由Controller來(lái)根據(jù)業(yè)務(wù)需求調(diào)配,,所以Controller還負(fù)擔(dān)了一個(gè)數(shù)據(jù)流調(diào)配的功能,。正在我寫(xiě)這篇文章的時(shí)候,我看到InfoQ發(fā)了這篇文章,,里面提到了一個(gè)移動(dòng)開(kāi)發(fā)中的痛點(diǎn)是:對(duì)MVC架構(gòu)劃分的理解,。我當(dāng)時(shí)沒(méi)能夠去參加這個(gè)座談會(huì),也沒(méi)辦法發(fā)表個(gè)人意見(jiàn),,所以就只能在這里寫(xiě)寫(xiě)了,。 在iOS開(kāi)發(fā)領(lǐng)域,我們應(yīng)當(dāng)如何進(jìn)行MVC的劃分,? 這里面其實(shí)有兩個(gè)問(wèn)題:
為什么我們會(huì)糾結(jié)于iOS開(kāi)發(fā)領(lǐng)域中MVC的劃分問(wèn)題,? 關(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è)來(lái)給出我的意見(jiàn),。 在服務(wù)端開(kāi)發(fā)領(lǐng)域,Controller和View的交互方式一般都是這樣,,比如Yii: /* ... 數(shù)據(jù)庫(kù)取數(shù)據(jù) ... 處理數(shù)據(jù) ... */ // 此處$this就是Controller $this->render("plan",array( 'planList' => $planList, 'plan_id' => $_GET['id'], )); 這里Controller和View之間區(qū)分得非常明顯,,Controller做完自己的事情之后,就把所有關(guān)于View的工作交給了頁(yè)面渲染引擎去做,,Controller不會(huì)去做任何關(guān)于View的事情,,包括生成View,這些都由渲染引擎代勞了,。這是一個(gè)區(qū)別,,但其實(shí)服務(wù)端View的概念和Native應(yīng)用View的概念,真正的區(qū)別在于:從概念上嚴(yán)格劃分的話,,服務(wù)端其實(shí)根本沒(méi)有View,,拜HTTP協(xié)議所賜,我們平時(shí)所討論的View只是用于描述View的字符串(更實(shí)質(zhì)的應(yīng)該稱之為數(shù)據(jù)),,真正的View是瀏覽器,。。 所以服務(wù)端只管生成對(duì)View的描述,,至于對(duì)View的長(zhǎng)相,,UI事件監(jiān)聽(tīng)和處理,都是瀏覽器負(fù)責(zé)生成和維護(hù)的,。但是在Native這邊來(lái)看,,原本屬于瀏覽器的任務(wù)也逃不掉要自己做。那么這件事情由誰(shuí)來(lái)做最合適,?蘋果給出的答案是: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)在是不是感覺(jué)清晰了很多,?如果再要說(shuō)詳細(xì)一點(diǎn),我們平常所認(rèn)為的服務(wù)端MVC是這樣劃分的: 但事實(shí)上,,整套流程的MVC劃分是這樣: 由圖中可以看出,,我們服務(wù)端開(kāi)發(fā)在這個(gè)概念下,其實(shí)只涉及M和C的開(kāi)發(fā)工作,,瀏覽器作為View的容器,,負(fù)責(zé)View的展示和事件的監(jiān)聽(tīng)。那么對(duì)應(yīng)到iOS客戶端的MVC劃分上面來(lái),,就是這樣: 唯一區(qū)別在于,,View的容器在服務(wù)端,是由Browser負(fù)責(zé),,在整個(gè)網(wǎng)站的流程中,,這個(gè)容器放在Browser是非常合理的。在iOS客戶端,,View的容器是由UIViewController中的view負(fù)責(zé),,我也覺(jué)得蘋果做的這個(gè)選擇是非常正確明智的。 因?yàn)闉g覽器和服務(wù)端之間的關(guān)系非常松散,,而且他們分屬于兩個(gè)不同陣營(yíng),,服務(wù)端將對(duì)View的描述生成之后,交給瀏覽器去負(fù)責(zé)展示,,然而一旦view上有什么事件產(chǎn)生,,基本上是很少傳遞到服務(wù)器(也就是所謂的Controller)的(要傳也可以:AJAX),都是在瀏覽器這邊把事情都做掉,,所以在這種情況下,,View容器就適合放在瀏覽器(V)這邊。 但是在iOS開(kāi)發(fā)領(lǐng)域,,雖然也有讓View去監(jiān)聽(tīng)事件的做法,,但這種做法非常少,都是把事件回傳給Controller,,然后Controller再另行調(diào)度,。所以這時(shí)候,View的容器放在Controller就非常合適。Controller可以因?yàn)椴煌录漠a(chǎn)生去很方便地更改容器內(nèi)容,,比如加載失敗時(shí),,把容器內(nèi)容換成失敗頁(yè)面的View,無(wú)網(wǎng)絡(luò)時(shí),,把容器頁(yè)面換成無(wú)網(wǎng)絡(luò)的View等等,。 在iOS開(kāi)發(fā)領(lǐng)域中,怎樣才算是MVC劃分的正確姿勢(shì),? 這個(gè)問(wèn)題其實(shí)在上面已經(jīng)解答掉一部分了,,那么這個(gè)問(wèn)題的答案就當(dāng)是對(duì)上面問(wèn)題的一個(gè)總結(jié)吧。 M應(yīng)該做的事:
C應(yīng)該做的事:
V應(yīng)該做的事:
我通過(guò)與服務(wù)端MVC劃分的對(duì)比來(lái)回答了這兩個(gè)問(wèn)題,,之所以這么做,是因?yàn)槲抑烙泻芏鄆OS工程師之前是從服務(wù)端轉(zhuǎn)過(guò)來(lái)的,。我也是這樣,,在進(jìn)安居客之前,我也是做服務(wù)端開(kāi)發(fā)的,,在學(xué)習(xí)iOS的過(guò)程中,,我也曾經(jīng)對(duì)iOS領(lǐng)域的MVC劃分問(wèn)題產(chǎn)生過(guò)疑惑,我疑惑的點(diǎn)就是前面開(kāi)篇我猜測(cè)的點(diǎn),。如果有人問(wèn)我iOS中應(yīng)該怎么做MVC的劃分,,我就會(huì)像上面這么回答。 MVCS蘋果自身就采用的是這種架構(gòu)思路,,從名字也能看出,,也是基于MVC衍生出來(lái)的一套架構(gòu)。從概念上來(lái)說(shuō),,它拆分的部分是Model部分,,拆出來(lái)一個(gè)Store。這個(gè)Store專門負(fù)責(zé)數(shù)據(jù)存取,。但從實(shí)際操作的角度上講,,它拆開(kāi)的是Controller。 這算是瘦Model的一種方案,瘦Model只是專門用于表達(dá)數(shù)據(jù),,然后存儲(chǔ),、數(shù)據(jù)處理都交給外面的來(lái)做。MVCS使用的前提是,,它假設(shè)了你是瘦Model,,同時(shí)數(shù)據(jù)的存儲(chǔ)和處理都在Controller去做。所以對(duì)應(yīng)到MVCS,,它在一開(kāi)始就是拆分的Controller,。因?yàn)镃ontroller做了數(shù)據(jù)存儲(chǔ)的事情,就會(huì)變得非常龐大,,那么就把Controller專門負(fù)責(zé)存取數(shù)據(jù)的那部分抽離出來(lái),,交給另一個(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ì)此有過(guò)非常激烈的討論,主題就是Fat model, skinny controller?,F(xiàn)在關(guān)于這方面的討論已經(jīng)不多了,,然而直到今天胖Model和瘦Model哪個(gè)更好,業(yè)界也還沒(méi)有定論,,所以這算是目前業(yè)界懸而未解的一個(gè)爭(zhēng)議,。我很少看到國(guó)內(nèi)有討論這個(gè)的資料,所以在這里我打算補(bǔ)充一下什么叫胖Model什么叫瘦Model,。以及他們的爭(zhēng)論來(lái)源于何處,。 什么叫胖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里面是沒(méi)問(wèn)題的,。另一方面,弱業(yè)務(wù)重復(fù)出現(xiàn)的頻率要大于強(qiáng)業(yè)務(wù),,對(duì)復(fù)用性的要求更高,,如果這部分業(yè)務(wù)寫(xiě)在Controller,類似的代碼會(huì)灑得到處都是,,一旦弱業(yè)務(wù)有修改(弱業(yè)務(wù)修改頻率低不代表就沒(méi)有修改),,這個(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)越來(lái)越Fat,,最終難以維護(hù),。 什么叫瘦Model? 瘦Model只負(fù)責(zé)業(yè)務(wù)數(shù)據(jù)的表達(dá),,所有業(yè)務(wù)無(wú)論強(qiáng)弱一律扔到Controller,。瘦Model要達(dá)到的目的是,盡一切可能去編寫(xiě)細(xì)粒度Model,,然后配套各種helper類或方法來(lái)對(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ù)完全無(wú)關(guān),,它的數(shù)據(jù)可以交給任何一個(gè)能處理它數(shù)據(jù)的Helper或其他的對(duì)象,,來(lái)完成業(yè)務(wù),。在代碼遷移的時(shí)候獨(dú)立性很強(qiáng),很少會(huì)出現(xiàn)拔出蘿卜帶出泥的情況,。另外,,由于SlimModel只是數(shù)據(jù)表達(dá),對(duì)它進(jìn)行維護(hù)基本上是0成本,,軟件膨脹得再厲害,,SlimModel也不會(huì)大到哪兒去。 缺點(diǎn)就在于,,Helper這種做法也不見(jiàn)得很好,,這里有一篇文章批判了這個(gè)事情。另外,,由于Model的操作會(huì)出現(xiàn)在各種地方,,SlimModel在一定程度上違背了DRY(Don't Repeat Yourself)的思路,Controller仍然不可避免在一定程度上出現(xiàn)代碼膨脹,。 我的態(tài)度,?嗯,我會(huì)在本門心法這一節(jié)里面說(shuō),。 說(shuō)回來(lái),,MVCS是基于瘦Model的一種架構(gòu)思路,把原本Model要做的很多事情中的其中一部分關(guān)于數(shù)據(jù)存儲(chǔ)的代碼抽象成了Store,,在一定程度上降低了Controller的壓力,。 MVVMMVVM去年在業(yè)界討論得非常多,無(wú)論國(guó)內(nèi)還是國(guó)外都討論得非常熱烈,,尤其是在ReactiveCocoa這個(gè)庫(kù)成熟之后,,ViewModel和View的信號(hào)機(jī)制在iOS下終于有了一個(gè)相對(duì)優(yōu)雅的實(shí)現(xiàn)。MVVM本質(zhì)上也是從MVC中派生出來(lái)的思想,,MVVM著重想要解決的問(wèn)題是盡可能地減少Controller的任務(wù),。不管MVVM也好,MVCS也好,,他們的共識(shí)都是Controller會(huì)隨著軟件的成長(zhǎng),,變很大很難維護(hù)很難測(cè)試。只不過(guò)兩種架構(gòu)思路的前提不同,,MVCS是認(rèn)為Controller做了一部分Model的事情,,要把它拆出來(lái)變成Store,MVVM是認(rèn)為Controller做了太多數(shù)據(jù)加工的事情,,所以MVVM把數(shù)據(jù)加工的任務(wù)從Controller中解放了出來(lái),,使得Controller只需要專注于數(shù)據(jù)調(diào)配的工作,ViewModel則去負(fù)責(zé)數(shù)據(jù)加工并通過(guò)通知機(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è)說(shuō)法并不矛盾,因?yàn)榕諱odel做的事情也是為Controller減負(fù),。 另外,,我前面說(shuō)MVVM把數(shù)據(jù)加工的任務(wù)從Controller中解放出來(lái),跟MVVM拆分的是胖Model也不矛盾,。要做到解放Controller,,首先你得有個(gè)胖Model,然后再把這個(gè)胖Model拆成Model和ViewModel,。 那么MVVM究竟應(yīng)該如何實(shí)現(xiàn),? 這很有可能是大多數(shù)人糾結(jié)的問(wèn)題,我打算憑我的個(gè)人經(jīng)驗(yàn)試圖在這里回答這個(gè)問(wèn)題,,歡迎交流,。 在iOS領(lǐng)域大部分MVVM架構(gòu)都會(huì)使用ReactiveCocoa,但是使用ReactiveCocoa的iOS應(yīng)用就是基于MVVM架構(gòu)的嗎,?那當(dāng)然不是,,我覺(jué)得很多人都存在這個(gè)誤區(qū),我面試過(guò)的一些人提到了ReactiveCocoa也提到了MVVM,,但他們對(duì)此的理解膚淺得讓我忍俊不禁,。嗯,在網(wǎng)絡(luò)層架構(gòu)我會(huì)舉出不使用ReactiveCocoa的例子,,現(xiàn)在舉我感覺(jué)有點(diǎn)兒早,。 MVVM的關(guān)鍵是要有View Model!而不是ReactiveCocoa 注:MVVM要有ViewModel,,以及ReactiveCocoa帶來(lái)的信號(hào)通知效果,,在ReactiveCocoa里就是RAC等相關(guān)宏來(lái)實(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ù)字我隨便寫(xiě)的不要太在意,。然后你有一個(gè)模塊是地圖模塊,,把經(jīng)緯度數(shù)組全部都轉(zhuǎn)變成MKAnnotation或其派生類對(duì)于Controller來(lái)說(shuō)是弱業(yè)務(wù),,(記住,胖Model就是用來(lái)做弱業(yè)務(wù)的),,因此我們用ViewModel直接把它轉(zhuǎn)變成MKAnnotation的NSArray,,交給Controller之后Controller直接就可以用了。 嗯,,這就是ViewModel要做的事情,,是不是覺(jué)得很簡(jiǎn)單,看不出優(yōu)越性,? 安居客Pad應(yīng)用也有一個(gè)地圖模塊,,在這里我設(shè)計(jì)了一個(gè)對(duì)象叫做reformer(其實(shí)就是ViewModel),專門用來(lái)干這個(gè)事情,。那么這么做的優(yōu)越性體現(xiàn)在哪兒呢,? 安居客分三大業(yè)務(wù):租房、二手房,、新房,。這三個(gè)業(yè)務(wù)對(duì)應(yīng)移動(dòng)開(kāi)發(fā)團(tuán)隊(duì)有三個(gè)API開(kāi)發(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不可能寫(xiě)三個(gè),,所以肯定少不了要有一個(gè)API數(shù)據(jù)兼容的邏輯,這個(gè)邏輯我就放在reformer里面去做了,,于是業(yè)務(wù)流程就變成了這樣: 這么一來(lái),,原本復(fù)雜的MKAnnotation組裝邏輯就從Controller里面拆分了出來(lái),Controller可以直接拿著Reformer返回的數(shù)據(jù)進(jìn)行展示,。APIManager就屬于Model,,reformer就屬于ViewModel。具體關(guān)于reformer的東西我會(huì)放在網(wǎng)絡(luò)層架構(gòu)來(lái)詳細(xì)解釋,。Reformer此時(shí)扮演的ViewModel角色能夠很好地給Controller減負(fù),,同時(shí),維護(hù)成本也大大降低,,經(jīng)過(guò)reformer產(chǎn)出的永遠(yuǎn)都是MKAnnotation,,Controller可以直接拿來(lái)使用。 然后另外一點(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ù)",,只不過(guò)這里的"數(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里面分出來(lái)的一部分),,所以View并不適合直接持有ViewModel,,那么View一旦產(chǎn)生數(shù)據(jù)了怎么辦?扔信號(hào)扔給ViewModel,,用誰(shuí)扔,?ReactiveCocoa。 在MVVM中使用ReactiveCocoa的第一個(gè)目的就是如上所說(shuō),,View并不適合直接持有ViewModel,。第二個(gè)目的就在于,ViewModel有可能并不是只服務(wù)于特定的一個(gè)View,,使用更加松散的綁定關(guān)系能夠降低ViewModel和View之間的耦合度,。 那么在MVVM中,Controller扮演什么角色,? 大部分國(guó)內(nèi)外資料闡述MVVM的時(shí)候都是這樣排布的:View <-> ViewModel <-> Model,,造成了MVVM不需要Controller的錯(cuò)覺(jué),現(xiàn)在似乎發(fā)展成業(yè)界開(kāi)始出現(xiàn)MVVM是不需要Controller的,。的聲音了,。其實(shí)MVVM是一定需要Controller的參與的,雖然MVVM在一定程度上弱化了Controller的存在感,,并且給Controller做了減負(fù)瘦身(這也是MVVM的主要目的),。但是,這并不代表MVVM中不需要Controller,,MMVC和MVVM他們之間的關(guān)系應(yīng)該是這樣: (來(lái)源:http://www./2014/12/06/reactivecocoa-mvvm-introduction/) View <-> C <-> ViewModel <-> Model,,所以使用MVVM之后,,就不需要Controller的說(shuō)法是不正確的,。嚴(yán)格來(lái)說(shuō)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果本身并沒(méi)有提供一個(gè)比較適合這種情況的綁定方法。iOS領(lǐng)域里KVO,,Notification,,block,delegate和target-action都可以用來(lái)做數(shù)據(jù)通信,,從而來(lái)實(shí)現(xiàn)綁定,,但都不如ReactiveCocoa提供的RACSignal來(lái)的優(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層來(lái)裝各種ViewModel,,也是屬于相對(duì)合理的結(jié)構(gòu),。 所以Controller在MVVM中,一方面負(fù)責(zé)View和ViewModel之間的綁定,,另一方面也負(fù)責(zé)常規(guī)的UI邏輯處理,。 VIPERVIPER(View,Interactor,,Presenter,,Entity,Routing)。VIPER我并沒(méi)有實(shí)際使用過(guò),,我是在objc.io上第13期看到的,。 但凡出現(xiàn)一個(gè)新架構(gòu)或者我之前并不熟悉的新架構(gòu),有一點(diǎn)我能夠非??隙?,這貨一定又是把MVC的哪個(gè)部分給拆開(kāi)了(壞笑,做這種判斷的理論依據(jù)在第一篇文章里面我已經(jīng)講過(guò)了),。事實(shí)情況是VIPER確實(shí)拆了很多很多,,除了View沒(méi)拆,其它的都拆了,。 我提到的這兩篇文章關(guān)于VIPER都講得很詳細(xì),,一看就懂。但具體在使用VIPER的時(shí)候會(huì)有什么坑或者會(huì)有哪些爭(zhēng)議我不是很清楚,,硬要寫(xiě)這一節(jié)的話我只能靠YY,,所以我想想還是算了。如果各位讀者有誰(shuí)在實(shí)際App中采用VIPER架構(gòu)的或者對(duì)VIPER很有興趣的,,可以評(píng)論區(qū)里面提出來(lái),,我們交流一下。 編后語(yǔ)為了更好地向讀者輸出更優(yōu)質(zhì)的內(nèi)容,,InfoQ將精選來(lái)自國(guó)內(nèi)外的優(yōu)秀文章,,經(jīng)過(guò)整理審校后,發(fā)布到網(wǎng)站,。本篇文章作者為田偉宇,,原文鏈接為Casa Taloyum。本文已由原作者授權(quán)InfoQ中文站轉(zhuǎn)載,。 感謝徐川對(duì)本文的審校,。 給InfoQ中文站投稿或者參與內(nèi)容翻譯工作,請(qǐng)郵件至editors@cn.,。也歡迎大家通過(guò)新浪微博(@InfoQ,,@丁曉昀),微信(微信號(hào):InfoQChina)關(guān)注我們,,并與我們的編輯和其他讀者朋友交流(歡迎加入InfoQ讀者交流群),。 |
|