<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                > 原文出處:http://www.infoq.com/cn/articles/ios-app-arch-2-2 iOS客戶端應用架構看似簡單,但實際上要考慮的事情不少。本文作者將以系列文章的形式來回答iOS應用架構中的種種問題,本文是其中的第二篇,主要講View層的組織和調用方案。中篇主要討論MVC、MVCS、MVVM、VIPER等架構在iOS開發中的應用。 ## 關于MVC、MVVM等一大堆思想 其實這些都是相對通用的思想,萬變不離其宗的還是在開篇里面我提到的那三個角色:數據管理者,數據加工者,數據展示者。這些五花八門的思想,不外乎就是制訂了一個規范,規定了這三個角色應當如何進行數據交換。但同時這些也是爭議最多的話題,所以我在這里來把幾個主流思想做一個梳理,當你在做View層架構時,能夠有個比較好的參考。 ### MVC MVC(Model-View-Controller)是最老牌的的思想,老牌到4人幫的書里把它歸成了一種模式,其中Model就是作為數據管理者,View作為數據展示者,Controller作為數據加工者,Model和View又都是由Controller來根據業務需求調配,所以Controller還負擔了一個數據流調配的功能。正在我寫這篇文章的時候,我看到InfoQ發了[這篇文章](http://www.infoq.com/cn/news/2015/04/symposium-web-mvc),里面提到了一個移動開發中的痛點是:對MVC架構劃分的理解。我當時沒能夠去參加這個座談會,也沒辦法發表個人意見,所以就只能在這里寫寫了。 **在iOS開發領域,我們應當如何進行MVC的劃分?** 這里面其實有兩個問題: 1. 為什么我們會糾結于iOS開發領域中MVC的劃分問題? 2. 在iOS開發領域中,怎樣才算是劃分的正確姿勢? **為什么我們會糾結于iOS開發領域中MVC的劃分問題?** 關于這個,每個人糾結的點可能不太一樣,我也不知道當時座談會上大家的觀點。但請允許我猜一下:是不是因為UIViewController中自帶了一個View,且控制了View的整個生命周期(viewDidLoad,viewWillAppear...),而在常識中我們都知道Controller不應該和View有如此緊密的聯系,所以才導致大家對劃分產生困惑?,下面我會針對這個猜測來給出我的意見。 在服務端開發領域,Controller和View的交互方式一般都是這樣,比如Yii: ~~~ /* ... 數據庫取數據 ... 處理數據 ... */ // 此處$this就是Controller $this->render("plan",array( 'planList' => $planList, 'plan_id' => $_GET['id'], )); ~~~ 這里Controller和View之間區分得非常明顯,Controller做完自己的事情之后,就把所有關于View的工作交給了頁面渲染引擎去做,Controller不會去做任何關于View的事情,包括生成View,這些都由渲染引擎代勞了。這是一個區別,但其實服務端View的概念和Native應用View的概念,真正的區別在于:從概念上嚴格劃分的話,服務端其實根本沒有View,拜HTTP協議所賜,我們平時所討論的View只是用于描述View的字符串(更實質的應該稱之為數據),真正的View是瀏覽器。。 所以服務端只管生成對View的描述,至于對View的長相,UI事件監聽和處理,都是瀏覽器負責生成和維護的。但是在Native這邊來看,原本屬于瀏覽器的任務也逃不掉要自己做。那么這件事情由誰來做最合適?蘋果給出的答案是:**UIViewController**。 鑒于蘋果在這一層做了很多艱苦卓絕的努力,讓iOS工程師們不必親自去實現這些內容。而且,它把所有的功能都放在了UIView上,并且把UIView做成不光可以展示UI,還可以作為容器的一個對象。 看到這兒你明白了嗎?UIView的另一個身份其實是容器!UIViewController中自帶的那個view,它的主要任務就是作為一個容器。如果它所有的相關命名都改成ViewContainer,那么代碼就會變成這樣: ~~~ - (void)viewContainerDidLoad { [self.viewContainer addSubview:self.label]; [self.viewContainer addSubview:self.tableView]; [self.viewContainer addSubview:self.button]; [self.viewContainer addSubview:self.textField]; } ... ... ~~~ 僅僅改了個名字,現在是不是感覺清晰了很多?如果再要說詳細一點,我們平常所認為的服務端MVC是這樣劃分的: ![](https://box.kancloud.cn/2015-09-15_55f7de38ca593.jpg) 但事實上,整套流程的MVC劃分是這樣: ![](https://box.kancloud.cn/2015-09-15_55f7de3972a03.jpg) 由圖中可以看出,我們服務端開發在這個概念下,其實只涉及M和C的開發工作,瀏覽器作為View的容器,負責View的展示和事件的監聽。那么對應到iOS客戶端的MVC劃分上面來,就是這樣: ![](https://box.kancloud.cn/2015-09-15_55f7de3a0fde3.jpg) 唯一區別在于,View的容器在服務端,是由Browser負責,在整個網站的流程中,這個容器放在Browser是非常合理的。在iOS客戶端,View的容器是由UIViewController中的view負責,我也覺得蘋果做的這個選擇是非常正確明智的。 因為瀏覽器和服務端之間的關系非常松散,而且他們分屬于兩個不同陣營,服務端將對View的描述生成之后,交給瀏覽器去負責展示,然而一旦view上有什么事件產生,基本上是很少傳遞到服務器(也就是所謂的Controller)的(要傳也可以:AJAX),都是在瀏覽器這邊把事情都做掉,所以在這種情況下,View容器就適合放在瀏覽器(V)這邊。 但是在iOS開發領域,雖然也有讓View去監聽事件的做法,但這種做法非常少,都是把事件回傳給Controller,然后Controller再另行調度。所以這時候,View的容器放在Controller就非常合適。Controller可以因為不同事件的產生去很方便地更改容器內容,比如加載失敗時,把容器內容換成失敗頁面的View,無網絡時,把容器頁面換成無網絡的View等等。 **在iOS開發領域中,怎樣才算是MVC劃分的正確姿勢?** 這個問題其實在上面已經解答掉一部分了,那么這個問題的答案就當是對上面問題的一個總結吧。 M應該做的事: * 給ViewController提供數據 * 給ViewController存儲數據提供接口 * 提供經過抽象的業務基本組件,供Controller調度 C應該做的事: * 管理View Container的生命周期 * 負責生成所有的View實例,并放入View Container * 監聽來自View與業務有關的事件,通過與Model的合作,來完成對應事件的業務。 V應該做的事: * 響應與業務無關的事件,并因此引發動畫效果,點擊反饋(如果合適的話,盡量還是放在View去做)等。 * 界面元素表達 我通過與服務端MVC劃分的對比來回答了這兩個問題,之所以這么做,是因為我知道有很多iOS工程師之前是從服務端轉過來的。我也是這樣,在進安居客之前,我也是做服務端開發的,在學習iOS的過程中,我也曾經對iOS領域的MVC劃分問題產生過疑惑,我疑惑的點就是前面開篇我猜測的點。如果有人問我iOS中應該怎么做MVC的劃分,我就會像上面這么回答。 ### MVCS 蘋果自身就采用的是這種架構思路,從名字也能看出,也是基于MVC衍生出來的一套架構。從概念上來說,它拆分的部分是Model部分,拆出來一個Store。這個Store專門負責數據存取。但從實際操作的角度上講,它拆開的是Controller。 這算是瘦Model的一種方案,瘦Model只是專門用于表達數據,然后存儲、數據處理都交給外面的來做。MVCS使用的前提是,它假設了你是瘦Model,同時數據的存儲和處理都在Controller去做。所以對應到MVCS,它在一開始就是拆分的Controller。因為Controller做了數據存儲的事情,就會變得非常龐大,那么就把Controller專門負責存取數據的那部分抽離出來,交給另一個對象去做,這個對象就是Store。這么調整之后,整個結構也就變成了真正意義上的MVCS。 **關于胖Model和瘦Model** 我在面試和跟別人聊天時,發現知道胖Model和瘦Model的概念的人不是很多。大約兩三年前國外業界曾經對此有過非常激烈的討論,主題就是Fat model, skinny controller。現在關于這方面的討論已經不多了,然而直到今天胖Model和瘦Model哪個更好,業界也還沒有定論,所以這算是目前業界懸而未解的一個爭議。我很少看到國內有討論這個的資料,所以在這里我打算補充一下什么叫胖Model什么叫瘦Model。以及他們的爭論來源于何處。 **什么叫胖Model?** 胖Model包含了部分弱業務邏輯。胖Model要達到的目的是,Controller從胖Model這里拿到數據之后,不用額外做操作或者只要做非常少的操作,就能夠將數據直接應用在View上。舉個例子: ~~~ Raw Data: timestamp:1234567 FatModel: @property (nonatomic, assign) CGFloat timestamp; - (NSString *)ymdDateString; // 2015-04-20 15:16 - (NSString *)gapString; // 3分鐘前、1小時前、一天前、2015-3-13 12:34 Controller: self.dateLabel.text = [FatModel ymdDateString]; self.gapLabel.text = [FatModel gapString]; ~~~ 把timestamp轉換成具體業務上所需要的字符串,這屬于業務代碼,算是弱業務。FatModel做了這些弱業務之后,Controller就能變得非常skinny,Controller只需要關注強業務代碼就行了。眾所周知,強業務變動的可能性要比弱業務大得多,弱業務相對穩定,所以弱業務塞進Model里面是沒問題的。另一方面,弱業務重復出現的頻率要大于強業務,對復用性的要求更高,如果這部分業務寫在Controller,類似的代碼會灑得到處都是,一旦弱業務有修改(弱業務修改頻率低不代表就沒有修改),這個事情就是一個災難。如果塞到Model里面去,改一處很多地方就能跟著改,就能避免這場災難。 然而其缺點就在于,胖Model相對比較難移植,雖然只是包含弱業務,但好歹也是業務,遷移的時候很容易拔出蘿卜帶出泥。另外一點,MVC的架構思想更加傾向于Model是一個Layer,而不是一個Object,不應該把一個Layer應該做的事情交給一個Object去做。最后一點,軟件是會成長的,FatModel很有可能隨著軟件的成長越來越Fat,最終難以維護。 **什么叫瘦Model?** 瘦Model只負責業務數據的表達,所有業務無論強弱一律扔到Controller。瘦Model要達到的目的是,盡一切可能去編寫細粒度Model,然后配套各種helper類或方法來對弱業務做抽象,強業務依舊交給Controller。舉個例子: ~~~ 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跟業務完全無關,它的數據可以交給任何一個能處理它數據的Helper或其他的對象,來完成業務。在代碼遷移的時候獨立性很強,很少會出現拔出蘿卜帶出泥的情況。另外,由于SlimModel只是數據表達,對它進行維護基本上是0成本,軟件膨脹得再厲害,SlimModel也不會大到哪兒去。 缺點就在于,Helper這種做法也不見得很好,這里有一篇[文章](http://nicksda.apotomo.de/2011/10/rails-misapprehensions-helpers-are-shit/)批判了這個事情。另外,由于Model的操作會出現在各種地方,SlimModel在一定程度上違背了DRY(Don't Repeat Yourself)的思路,Controller仍然不可避免在一定程度上出現代碼膨脹。 我的態度?嗯,我會在本門心法這一節里面說。 說回來,MVCS是基于瘦Model的一種架構思路,把原本Model要做的很多事情中的其中一部分關于數據存儲的代碼抽象成了Store,在一定程度上降低了Controller的壓力。 ### MVVM MVVM去年在業界討論得非常多,無論國內還是國外都討論得非常熱烈,尤其是在ReactiveCocoa這個庫成熟之后,ViewModel和View的信號機制在iOS下終于有了一個相對優雅的實現。MVVM本質上也是從MVC中派生出來的思想,MVVM著重想要解決的問題是盡可能地減少Controller的任務。不管MVVM也好,MVCS也好,他們的共識都是Controller會隨著軟件的成長,變很大很難維護很難測試。只不過兩種架構思路的前提不同,MVCS是認為Controller做了一部分Model的事情,要把它拆出來變成Store,MVVM是認為Controller做了太多數據加工的事情,所以MVVM把數據加工的任務從Controller中解放了出來,使得Controller只需要專注于數據調配的工作,ViewModel則去負責數據加工并通過通知機制讓View響應ViewModel的改變。 MVVM是基于胖Model的架構思路建立的,然后在胖Model中拆出兩部分:Model和ViewModel。關于這個觀點我要做一個額外解釋:胖Model做的事情是先為Controller減負,然后由于Model變胖,再在此基礎上拆出ViewModel,跟業界普遍認知的MVVM本質上是為Controller減負這個說法并不矛盾,因為胖Model做的事情也是為Controller減負。 另外,我前面說MVVM把數據加工的任務從Controller中解放出來,跟MVVM拆分的是胖Model也不矛盾。要做到解放Controller,首先你得有個胖Model,然后再把這個胖Model拆成Model和ViewModel。 **那么MVVM究竟應該如何實現?** 這很有可能是大多數人糾結的問題,我打算憑我的個人經驗試圖在這里回答這個問題,歡迎交流。 在iOS領域大部分MVVM架構都會使用ReactiveCocoa,但是使用ReactiveCocoa的iOS應用就是基于MVVM架構的嗎?那當然不是,我覺得很多人都存在這個誤區,我面試過的一些人提到了ReactiveCocoa也提到了MVVM,但他們對此的理解膚淺得讓我忍俊不禁。嗯,在網絡層架構我會舉出不使用ReactiveCocoa的例子,現在舉我感覺有點兒早。 **MVVM的關鍵是要有View Model!而不是ReactiveCocoa** 注:MVVM要有ViewModel,以及ReactiveCocoa帶來的信號通知效果,在ReactiveCocoa里就是RAC等相關宏來實現。另外,使用ReactiveCocoa能夠比較優雅地實現MVVM模式,就是因為有RAC等相關宏的存在。就像它的名字一樣Reactive-響應式,這也是區分MVVM的VM和MVC的C和MVP的P的一個重要方面。 ViewModel做什么事情?就是把RawData變成直接能被View使用的對象的一種Model。舉個例子: ~~~ Raw Data: { ( (123, 456), (234, 567), (345, 678) ) } ~~~ 這里的RawData我們假設是經緯度,數字我隨便寫的不要太在意。然后你有一個模塊是地圖模塊,把經緯度數組全部都轉變成MKAnnotation或其派生類對于Controller來說是弱業務,(記住,胖Model就是用來做弱業務的),因此我們用ViewModel直接把它轉變成MKAnnotation的NSArray,交給Controller之后Controller直接就可以用了。 嗯,這就是ViewModel要做的事情,是不是覺得很簡單,看不出優越性? 安居客Pad應用也有一個地圖模塊,在這里我設計了一個對象叫做reformer(其實就是ViewModel),專門用來干這個事情。那么這么做的優越性體現在哪兒呢? 安居客分三大業務:租房、二手房、新房。這三個業務對應移動開發團隊有三個API開發團隊,他們各自為政,這就造成了一個結果:三個API團隊回饋給移動客戶端的數據內容雖然一致,但是數據格式是不一致的,也就是相同value對應的key是不一致的。但展示地圖的ViewController不可能寫三個,所以肯定少不了要有一個API數據兼容的邏輯,這個邏輯我就放在reformer里面去做了,于是業務流程就變成了這樣: ![](https://box.kancloud.cn/2015-09-15_55f7de3ac09af.jpg) 這么一來,原本復雜的MKAnnotation組裝邏輯就從Controller里面拆分了出來,Controller可以直接拿著Reformer返回的數據進行展示。APIManager就屬于Model,reformer就屬于ViewModel。具體關于reformer的東西我會放在網絡層架構來詳細解釋。Reformer此時扮演的ViewModel角色能夠很好地給Controller減負,同時,維護成本也大大降低,經過reformer產出的永遠都是MKAnnotation,Controller可以直接拿來使用。 然后另外一點,還有一個業務需求是取附近的房源,地圖API請求是能夠hold住這個需求的,那么其他地方都不用變,在fetchDataWithReformer的時候換一個reformer就可以了,其他的事情都交給reformer。 **那么ReactiveCocoa應該扮演什么角色?** 不用ReactiveCocoa也能MVVM,用ReactiveCocoa能更好地體現MVVM的精髓。前面我舉到的例子只是數據從API到View的方向,View的操作也會產生"數據",只不過這里的"數據"更多的是體現在表達用戶的操作上,比如輸入了什么內容,那么數據就是text、選擇了哪個cell,那么數據就是indexPath。那么在數據從view走向API或者Controller的方向上,就是ReactiveCocoa發揮的地方。 我們知道,ViewModel本質上算是Model層(因為是胖Model里面分出來的一部分),所以View并不適合直接持有ViewModel,那么View一旦產生數據了怎么辦?扔信號扔給ViewModel,用誰扔?ReactiveCocoa。 在MVVM中使用ReactiveCocoa的第一個目的就是如上所說,View并不適合直接持有ViewModel。第二個目的就在于,ViewModel有可能并不是只服務于特定的一個View,使用更加松散的綁定關系能夠降低ViewModel和View之間的耦合度。 **那么在MVVM中,Controller扮演什么角色?** 大部分國內外資料闡述MVVM的時候都是這樣排布的:View ViewModel Model,造成了MVVM不需要Controller的錯覺,現在似乎發展成業界開始出現MVVM是不需要Controller的。的聲音了。其實MVVM是一定需要Controller的參與的,雖然MVVM在一定程度上弱化了Controller的存在感,并且給Controller做了減負瘦身(這也是MVVM的主要目的)。但是,這并不代表MVVM中不需要Controller,MMVC和MVVM他們之間的關系應該是這樣: ![](https://box.kancloud.cn/2015-09-15_55f7de3b5e440.gif) (來源:[http://www.sprynthesis.com/2014/12/06/reactivecocoa-mvvm-introduction/](http://www.sprynthesis.com/2014/12/06/reactivecocoa-mvvm-introduction/)) View C ViewModel Model,所以使用MVVM之后,就不需要Controller的說法是不正確的。嚴格來說MVVM其實是MVCVM。從圖中可以得知,Controller夾在View和ViewModel之間做的其中一個主要事情就是將View和ViewModel進行綁定。在邏輯上,Controller知道應當展示哪個View,Controller也知道應當使用哪個ViewModel,然而View和ViewModel它們之間是互相不知道的,所以Controller就負責控制他們的綁定關系,所以叫Controller/控制器就是這個原因。 前面扯了那么多,其實歸根結底就是一句話:在MVC的基礎上,把C拆出一個ViewModel專門負責數據處理的事情,就是MVVM。然后,為了讓View和ViewModel之間能夠有比較松散的綁定關系,于是我們使用ReactiveCocoa,因為蘋果本身并沒有提供一個比較適合這種情況的綁定方法。iOS領域里KVO,Notification,block,delegate和target-action都可以用來做數據通信,從而來實現綁定,但都不如ReactiveCocoa提供的RACSignal來的優雅,如果不用ReactiveCocoa,綁定關系可能就做不到那么松散那么好,但并不影響它還是MVVM。 在實際iOS應用架構中,MVVM應該出現在了大部分創業公司或者老牌公司新App的iOS應用架構圖中,據我所知易寶支付旗下的某個iOS應用就整體采用了MVVM架構,他們抽出了一個Action層來裝各種ViewModel,也是屬于相對合理的結構。 所以Controller在MVVM中,一方面負責View和ViewModel之間的綁定,另一方面也負責常規的UI邏輯處理。 ### VIPER VIPER(View,Interactor,Presenter,Entity,Routing)。VIPER我并沒有實際使用過,我是在objc.io上第13期看到的。 但凡出現一個新架構或者我之前并不熟悉的新架構,有一點我能夠非常肯定,這貨一定又是把MVC的哪個部分給拆開了(壞笑,做這種判斷的理論依據在第一篇文章里面我已經講過了)。事實情況是VIPER確實拆了很多很多,除了View沒拆,其它的都拆了。 我提到的這兩篇文章關于VIPER都講得很詳細,一看就懂。但具體在使用VIPER的時候會有什么坑或者會有哪些爭議我不是很清楚,硬要寫這一節的話我只能靠YY,所以我想想還是算了。如果各位讀者有誰在實際App中采用VIPER架構的或者對VIPER很有興趣的,可以評論區里面提出來,我們交流一下。 ## 編后語 為了更好地向讀者輸出更優質的內容,InfoQ將精選來自國內外的優秀文章,經過整理審校后,發布到網站。本篇文章作者為**田偉宇**,原文鏈接為[Casa Taloyum](http://casatwy.com/iosying-yong-jia-gou-tan-viewceng-de-zu-zhi-he-diao-yong-fang-an.html)。本文已由原作者授權InfoQ中文站轉載。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看