1. 程式人生 > >iOS開發-進階:被誤解的MVC和被神化的MVVM(作者:唐巧)

iOS開發-進階:被誤解的MVC和被神化的MVVM(作者:唐巧)

作者 唐巧 釋出於 2015年11月1日 |

被誤解的 MVC

MVC 的歷史

MVC,全稱是 Model View Controller,是模型 (model)-檢視 (view)-控制器 (controller) 的縮寫。它表示的是一種常見的客戶端軟體開發框架。

MVC 的概念最早出現在二十世紀八十年代的 施樂帕克 實驗室中(對,就是那個發明圖形使用者介面和滑鼠的實驗室),當時施樂帕克為 Smalltalk 發明了這種軟體設計模式。

現在,MVC 已經成為主流的客戶端程式設計框架,在 iOS 開發中,系統為我們實現好了公共的檢視類:UIView,和控制器類:UIViewController。大多數時候,我們都需要繼承這些類來實現我們的程式邏輯,因此,我們幾乎逃避不開 MVC 這種設計模式。

但是,幾十年過去了,我們對於 MVC 這種設計模式真的用得好嗎?其實不是的,MVC 這種分層方式雖然清楚,但是如果使用不當,很可能讓大量程式碼都集中在 Controller 之中,讓 MVC 模式變成了 Massive View Controller 模式。

Controller 的臃腫問題何解?

很多人試圖解決 MVC 這種架構下 Controller 比較臃腫的問題。我還記得半年前 InfoQ 搞了一次移動座談會,當時 BeeFramework 和 Samurai-Native 的作者 老郭 問了我一句話:「什麼樣的內容才應該放到 Controller 中?」。但是當時因為時間不夠,我沒能展開我的觀點,這次正好在這裡好好談談我對於這個問題的想法。

我們來看看 MVC 這種架構的特點。其實設計模式很多時候是為了 Don't repeat yourself 原則來做的,該原則要求能夠複用的程式碼要儘量複用,來保證重用。在 MVC 這種設計模式中,我們發現 View 和 Model 都是符合這種原則的。

對於 View 來說,你如果抽象得好,那麼一個 App 的動畫效果可以很方便地移植到別的 App 上,而 Github 上也有很多 UI 控制元件,這些控制元件都是在 View 層做了很好的封裝設計,使得它能夠方便地開源給大家複用。

對於 Model 來說,它其實是用來儲存業務的資料的,如果做得好,它也可以方便地複用。比如我當時在做有道雲筆記 iPad 版的時候,我們就直接和 iOS 版複用了所有的 Model 層的程式碼。在創業做猿題庫客戶端時,iOS 和 iPad 版的 Model 層程式碼再次被複用上了。當然,因為和業務本身的資料意義相關,Model 層的複用大多數是在一個產品內部,不太可能像 View 層那樣開源給社群。

說完 View 和 Model 了,那我們想想 Controller,Controller 有多少可以複用的?我們寫完了一個 Controller 之後,可以很方便地複用它嗎?結論是:非常難複用。在某些場景下,我們可能可以用addSubViewController 之類的方式複用 Controller,但它的複用場景還是非常非常少的。

如果我們能夠意識到 Controller 裡面的程式碼不便於複用,我們就能知道什麼程式碼應該寫在 Controller 裡面了,那就是那些不能複用的程式碼。在我看來,Controller 裡面就只應該存放這些不能複用的程式碼,這些程式碼包括:

  • 在初始化時,構造相應的 View 和 Model。
  • 監聽 Model 層的事件,將 Model 層的資料傳遞到 View 層。
  • 監聽 View 層的事件,並且將 View 層的事件轉發到 Model 層。

如果 Controller 只有以上的這些程式碼,那麼它的邏輯將非常簡單,而且也會非常短。

但是,我們卻很難做到這一點,因為還是有很多邏輯我們不知道寫在哪裡,於是就都寫到了 Controller 中了,那我們接下來就看看其它邏輯應該寫在哪裡。

如何對 ViewController 瘦身?

objc.io 是一個非常有名的 iOS 開發部落格,它上面的第一課 《Lighter View Controllers》 上就講了很多這樣的技巧,我們先總結一下它裡面的觀點:

  • 將 UITableView 的 Data Source 分離到另外一個類中。
  • 將資料獲取和轉換的邏輯分別到另外一個類中。
  • 將拼裝控制元件的邏輯,分離到另外一個類中。

你想明白了嗎?其實 MVC 雖然只有三層,但是它並沒有限制你只能有三層。所以,我們可以將 Controller 裡面過於臃腫的邏輯抽取出來,形成新的可複用模組或架構層次。

我個人對於邏輯的抽取,有以下總結。

將網路請求抽象到單獨的類中

新手寫程式碼,直接就在 Controller 裡面用 AFNetworking 發一個請求,請求的完資料直接就傳遞給 View。入門一些的同學,知道把這些請求程式碼移到另外一個靜態類裡面。但是我覺得還不夠,所以我建議將每一個網路請求直接封裝成類。

把每一個網路請求封裝成物件其實是使用了設計模式中的 Command 模式,它有以下好處:

  • 將網路請求與具體的第三方庫依賴隔離,方便以後更換底層的網路庫。實際上我們公司的 iOS 客戶端最初是基於 ASIHttpRequest 的,我們只花了兩天,就很輕鬆地切換到了 AFNetworking
  • 方便在基類中處理公共邏輯,例如猿題庫的資料版本號資訊就統一在基類中處理。
  • 方便在基類中處理快取邏輯,以及其它一些公共邏輯。
  • 方便做物件的持久化。

大家如果感興趣,可以看我們公司開源的 iOS 網路庫:YTKNetwork。它在這種思考的指導下,不但將 Controller 中的程式碼瘦身,而且進一步演化和加強,現在它還支援諸如複雜網路請求管理,斷點續傳,外掛機制,JSON 合法性檢查等功能。

這部分程式碼從 Controller 中剝離出來後,不但簡化了 Controller 中的邏輯,也達到了網路層的程式碼複用的效果。

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

新手寫程式碼,喜歡在 Controller 中把一個個 UILabel ,UIButton,UITextField 往 self.view 上用 addSubView 方法放。我建議大家可以用兩種辦法把這些程式碼從 Controller 中剝離。

方法一:構造專門的 UIView 的子類,來負責這些控制元件的拼裝。這是最徹底和優雅的方式,不過稍微麻煩一些的是,你需要把這些控制元件的事件回撥先接管,再都一一暴露回 Controller。

方法二:用一個靜態的 Util 類,幫助你做 UIView 的拼裝工作。這種方式稍微做得不太徹底,但是比較簡單。

對於一些能複用的 UI 控制元件,我建議用方法一。如果專案工程比較複雜,我也建議用方法一。如果專案太緊,另外相關專案的程式碼量也不多,可以嘗試方法二。

構造 ViewModel

誰說 MVC 就不能用 ViewModel 的?MVVM 的優點我們一樣可以借鑑。具體做法就是將 ViewController 給 View 傳遞資料這個過程,抽象成構造 ViewModel 的過程。

這樣抽象之後,View 只接受 ViewModel,而 Controller 只需要傳遞 ViewModel 這麼一行程式碼。而另外構造 ViewModel 的過程,我們就可以移動到另外的類中了。

在具體實踐中,我建議大家專門建立構造 ViewModel 工廠類,參見 工廠模式。另外,也可以專門將資料存取都抽將到一個 Service 層,由這層來提供 ViewModel 的獲取。

專門構造儲存類

剛剛說到 ViewModel 的構造可以抽獎到一個 Service 層。與此相應的,資料的儲存也應該由專門的物件來做。在小猿搜題專案中,我們由一個叫 UserAgent 的類,專門來處理本地資料的存取。

資料存取放在專門的類中,就可以針對存取做額外的事情了。比如:

  • 對一些熱點資料增加快取
  • 處理資料遷移相關的邏輯

如果要做得更細,可以把儲存引擎再抽象出一層。這樣你就可以方便地切換儲存的底層,例如從 sqlite 切換到 key-value 的儲存引擎等。

小結

通過程式碼的抽取,我們可以將原本的 MVC 設計模式中的 ViewController 進一步拆分,構造出 網路請求層、ViewModel 層、Service 層、Storage 層等其它類,來配合 Controller 工作,從而使 Controller 更加簡單,我們的 App 更容易維護。

另外,不知道大家注意到沒,其實 Controller 層是非常難於測試的,如果我們能夠將 Controller 瘦身,就可以更方便地寫 Unit Test 來測試各種與介面的無關的邏輯。移動端自動化測試框架都不太成熟,但是將 Controller 的程式碼抽取出來,是有助於我們做測試工作的。

希望本文能幫助大家掌握正確使用 MVC 的姿勢,在下一節裡,我將分享一下我對 MVVM 的看法。

被神化的 MVVM

MVVM 的歷史

MVVM 是 Model-View-ViewModel 的簡寫。

相對於 MVC 的歷史來說,MVVM 是一個相當新的架構,MVVM 最早於 2005 年被微軟的 WPF 和 Silverlight 的架構師 John Gossman 提出,並且應用在微軟的軟體開發中。當時 MVC 已經被提出了 20 多年了,可見兩者出現的年代差別有多大。

MVVM 在使用當中,通常還會利用雙向繫結技術,使得 Model 變化時,ViewModel 會自動更新,而 ViewModel 變化時,View 也會自動變化。所以,MVVM 模式有些時候又被稱作:model-view-binder 模式。

具體在 iOS 中,可以使用 KVO 或 Notification 技術達到這種效果。

MVVM 的神化

在使用中,我發現大家對於 MVVM 以及 MVVM 衍生出來的框架(比如 ReactiveCocoa)有一種「敬畏」感。這種「敬畏」感某種程度上就像對神一樣,這主要表現在我沒有聽到大家對於 MVVM 的任何批評。

我感覺原因首先是 MVVM 並沒有很大程度上普及,大家對於新技術一般都不熟,進而不敢妄加評論。另外,ReactiveCocoa 本身上手的複雜性,也讓很多人感覺到這種技術很高深難懂,進而加重了大家對它的「敬畏」。

MVVM 的作用和問題

MVVM 在實際使用中,確實能夠使得 Model 層和 View 層解耦,但是如果你需要實現 MVVM 中的雙向繫結的話,那麼通常就需要引入更多複雜的框架來實現了。

對此,MVVM 的作者 John Gossman 的 批評 應該是最為中肯的。John Gossman 對 MVVM 的批評主要有兩點:

第一點:資料繫結使得 Bug 很難被除錯。你看到介面異常了,有可能是你 View 的程式碼有 Bug,也可能是 Model 的程式碼有問題。資料繫結使得一個位置的 Bug 被快速傳遞到別的位置,要定位原始出問題的地方就變得不那麼容易了。

第二點:對於過大的專案,資料繫結需要花費更多的記憶體。

某種意義上來說,我認為就是資料繫結使得 MVVM 變得複雜和難用了。但是,這個缺點同時也被很多人認為是優點。

ReactiveCocoa

函數語言程式設計(Functional Programming)和響應式程式設計(React Programming)也是當前很火的兩個概念,它們的結合可以很方便地實現資料的繫結。於是,在 iOS 程式設計中,ReactiveCocoa 橫空出世了,它的概念都非常 新,包括:

  • 函數語言程式設計(Functional Programming),函式也變成一等公民了,可以擁有和物件同樣的功能,例如當成引數傳遞,當作返回值等。看看 Swift 語言帶來的眾多函數語言程式設計的特性,就你知道這多 Cool 了。
  • 響應式程式設計(React Programming),原來我們基於事件(Event)的處理方式都弱了,現在是基於輸入(在 ReactiveCocoa 裡叫 Signal)的處理方式。輸入還可以通過函數語言程式設計進行各種 Combine 或 Filter,盡顯各種靈活的處理。
  • 無狀態(Stateless),狀態是函式的魔鬼,無狀態使得函式能更好地測試。
  • 不可修改(Immutable),資料都是不可修改的,使得軟體邏輯簡單,也可以更好地測試。

哇,所有這些都太 Cool 了。當我看到的時候,我都雞凍了!

我們應該客觀評價 MVVM 和 ReactiveCocoa

但是但是,我突然想到,我好象只需要一個 ViewModel 而已,我完全可以簡單地做一個 ViewModel 的工廠類或 Service 類就可以了,為什麼要引入這麼多框架?現有的 MVC 真的有那麼大的問題嗎?

直到現在,ReactiveCocoa 在國內外還都是在小眾領域,沒有被大量接受成為主流的程式設計框架。不只是在 iOS 語言,在別的語言中,例如 Java 中的 RxJava 也同樣沒有成為主流。

我在這裡,不是想說 ReactiveCocoa 不好,也不是想說 MVVM 不好,而是想讓大家都能夠有一個客觀的認識。ReactiveCocoa 和 MVVM 不應該被神化,它是一種新穎的程式設計框架,能夠解決舊有程式設計框架的一些問題,但是也會帶來一些新問題,僅此而已。如果不能使好的駕馭 ReactiveCocoa,同樣會造成 Controller 程式碼過於複雜,程式碼邏輯不易維護的問題。

總結

有一些人總是追趕著技術,有什麼新技術不管三七二十一立馬就用,結果被各種坑。

又有一些人,總是擔心新技術帶來的技術風險,不願意學習。結果現在還有人在用 MRC 手動管理引用計數。

而我想說,我們需要保持的是一個擁抱變化的心,以及理性分析的態度。在新技術的面前,不盲從,也不守舊,一切的決策都應該建立在認真分析的基礎上,這樣才能應對技術的變化。


相關推薦

iOS開發-:誤解MVC神化MVVM(作者:)

作者 唐巧 釋出於 2015年11月1日 | 被誤解的 MVC MVC 的歷史 MVC,全稱是 Model View Controller,是模型 (model)-檢視 (view)-控制器 (controller) 的縮寫。它表示的是一種常見的客戶端軟

iOS開發》書籍目錄

archive 憑證 修改 工具 core serial 第二部分 破解 uilabel 第一部分:iOS開發工具 第二部分:iOS開發實踐 第10章 理解內存管理 10.1 引用計數 10.1.1 什麽是引用計數,原理是什麽 10.1.2 我們為什麽需要引用計數 10

iOS開發》閱讀總結

1.引用計數的作用 物件A向物件B傳遞引數物件M ,物件M可能成為物件B的成員變數,也可能只是臨時使用,如果不用引用計數控制,則無法在正確的時間釋放物件M。 案例一:物件A將物件M傳遞給物件B後

Reveal的使用--整理自的《iOS開發

1.下載一個正版的reveal來安裝。 2.開啟終端,輸入vim ~/.lldbinit建立一個名為.lldbinit的檔案,然後將如下內容輸入檔案中 command alias reveal_load_sim expr (void*)dlopen("/Applicat

iOS開發-使用多個StoryBoard劃分專案

前言:在實際來發中,作者一般都是使用純程式碼的方式進行開發,國內很多開發者都喜歡使用純程式碼進行開發,一方面是程式碼可維護性比較高,另一方面也是因為StoryBoard團隊協作的詬病。不過如果把一個專案拆分為多個StroyBoard,不同的人負責不同的模組

大疆無人機二次開發-DJI mobile SDKDJI onboard SDK聯合開發行業應用專案

2、如果要使用DJI onborad SDK,必須開啟無人機的API控制,下載大疆調參軟體DJI Assistant 2開啟API控制,將串列埠波特率調為230400,其餘如果不需要,可以選擇不傳送。 3、啟用你的key,將你電腦的UART串列埠(USB串列埠)與飛控的API口連線如下圖 只需要接123口

iOS開發-:最新版SDWebImage的使用

文章轉自: http://www.cocoachina.com/ios/20141212/10622.html 我之前寫過一篇部落格,介紹快取處理的三種方式,其中最難,最麻煩,最佔記憶體資源的還是圖片快取,最近做的專案有大量的圖片處理,還是採用了SDWebIma

iOS開發-:MOV格式視訊轉MP4格式

AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:sourceUrl options:nil]; NSArray *compatiblePresets = [AVAssetExportSession exportP

iOS開發-:音訊播放、錄音、視訊播放、拍照、視訊錄製

轉自: http://www.cnblogs.com/kenshincui/p/4186022.html#audioRecord 文章太長了, 如果我這裡寫的質量不好, 請參考原文; 概覽 隨著移動網際網路的發展,如今的手機早已不是打電話、發簡訊那麼

iOS開發-:開啟Remote notifications

需要在Xcode 中修改應用的 Capabilities 開啟Remote notifications,請參考下圖: 修改通知處理函式 當註冊了Backgroud Modes -> Remote notifications 後,notification 處理

iOS開發-:瀑布流基本實現

文章轉自: http://www.jianshu.com/p/78830bdb04a9 一、瀑布流設計方案 不可取.png 過於複雜.png 最優方案.png 二、瀑布流設計思路分析 1、自定義流水佈局中,指定滾動方向、預設列數、行間距、列間距、以及

iOS開發-UITapGestureRecognizer使用技巧

手勢互動是iOS開發中用的比較多的一個類,用途無處不在,這裡面也衍生了很多的需求和用法,UIGestureRecognizer很強大,它的子類包括很多,不過想要更完美的使用它,就需要了解它的底層原理和和一些特殊情況下的處理辦法,本文主要介紹UITapGestur

iOS開發-:JS與OC的互動

在移動應用的專案中, web 相比原生應用有如下優點: 版本可以隨時更新, 效率高;可動態配置要展示的資料, 及資料來源.原生應用中, 如果一個頁面的展示, 需要多次呼叫不同的網路請求, 並且, 上一次請求的結果是下一次請求的引數, 這樣按順序呼叫網路並且等待網路返回的資

iOS開發-:JPush設定標籤與別名的API

標籤與別名 API (iOS) 功能說明 溫馨提示,設定標籤別名請注意處理call back結果。 只有call back 返回值為 0 才設定成功,才可以向目標推送。否則伺服器 API 會返回1011錯誤。所有回撥函式都在主執行緒執行。 提供幾個相關 AP

ios開發之多執行緒01 執行緒 GCD

一 多執行緒基礎 什麼是程序? 程序是指在系統中正在執行的一個應用程式。 每個程序之間是獨立的,每個程序均執行在其專用且受保護的記憶體空間內。 什麼是執行緒? 1個程序要想執行任務,必須得有執行緒(每1個程序至少要有1條執行緒)。 1個執行緒中任務的執行

【原】iOS開發()讀書筆記(二)

第三部分:iOS開發底層原理 1、Objective-C物件模型 1.1 isa指標 NSObject.h部分程式碼: NS_ROOT_CLASS @interface NSObject <NSObject> { Class isa; } objc.h部分程式碼: typedef stru

Android十八 MVC、MVP、MVVM架構總結

一、MVVM概述 MVVM是一種軟體開發架構,是Model-View-View Model的縮寫,在Android中要實現MVVM架構, 需要使用Databinding的框架,Databinding即資料繫結,是Google為了能在Android上實現MVV

python3開發-Django框架中的ORM的常用操作的補充(F查詢Q查詢,事務)

這樣的 env atomic 實例 In git 必須 TE setup 閱讀目錄 F查詢和Q查詢 事務 一、F查詢和Q查詢 1、F查詢 查詢前的準備 class Product(models.Model): name = mod

python3開發-Django框架的ORM常用字段參數

情況 friend 間隔 .class SM 讀取文件 int 如果 字符串類型 閱讀目錄 常用字段 字段合集 自定義字段 字段參數 關系參數 多對多的關聯關系的三種方式 一、常用字段 AutoField int自增列,必須填入參數 prima

python3開發-Django框架的中間件的五種用法邏輯過程

meta alc 多個 img eth ble 方法 get action 閱讀目錄 什麽是中間件 中間件的執行流程 中間件的邏輯過程 一、什麽是中間件? 官方的說法:中間件是一個用來處理Django的請求和響應的框架級別的鉤子。它是一個輕量、