iOS VIPER架構實踐(一):從MVC到MVVM到VIPER

簡介

最近半年在寫app的時候,研究了一下各類iOS代碼架構,最後選擇了VIPER進行實踐,在此對實踐中遇到的各類設計問題作一番總結,並分享造出的輪子。html

對代碼風格和架構有興趣的同窗,確定都已經在不少地方見過各類架構的介紹。MVC、MVP、MVVM、VIPER,細分程度逐漸上升。這些架構設計都是來自MVC,只是各自用不一樣的方式對MVC進行了細分,在此只對MVC、MVP和MVVM做精簡介紹,想要詳細瞭解能夠參考這些文章:前端

iOS 架構模式–解密 MVC,MVP,MVVM以及VIPER架構,ios

淺談 MVC、MVP 和 MVVM 架構模式編程

MVC

Model-View-Controller。MVC簡單地將一個模塊分爲3部分:架構

  • View是展現給外部的界面
  • Model是Controller內部管理的數據模型
  • Controller負責將Model的變化更新到View
  • Controller負責處理來自View的事件

MVC的劃分粒度很粗,所以有不少種具體實現,各個實現有差別,所以並無一個十分明確的標準定義。app

蘋果的MVC

蘋果的Cocoa Touch就遵守了MVC的設計,一個界面分爲UIView和UIViewController,UIView負責渲染和接收觸摸事件,UIViewController負責子view之間的佈局、組合、更新以及事件處理。框架

儘管蘋果已經給咱們提供了簡單的MVC支持,可是在實踐中咱們卻經常沒有遵照MVC。緣由在於Cocoa Touch中的Model部分是由咱們本身負責管理的,並無提供原生的設計支持。因此有時候會出現這樣的狀況:一個UIView爲了方便,提供了一個從某個model進行配置的方法。乍一看十分合理,可是仔細想一想就會發現,這麼作已經將View和Model耦合,不符合蘋果官方的MVC規範(The Role of View Controllers)。dom

另外,UIViewController存在的一些問題,致使了它很容易變得臃腫和耦合。mvvm

首先,UIViewController和UIView耦合得十分緊密,致使UIViewController常常和某些具體的UIView耦合,幾乎沒法重用。並且在測試的時候,很難作到單獨測試沒有View的那部分代碼,由於在寫的時候就很容易將View的邏輯入侵到各處,Controller會受到View的狀態的影響,沒法穩定測試。所以,應該儘可能把和View無關的代碼放到UIViewController以外。函數

第二,UIViewController負責了界面跳轉的操做,界面跳轉的相關配置是直接在對應的UIViewController實例上設置的,這樣就很容易把源界面和目的界面耦合起來,簡單地把界面跳轉的部分單獨抽離爲一個封裝好的跳轉方法能夠必定程度上減小這部分耦合,但也不可避免地會多寫許多代碼。

所以,蘋果的MVC,其實是Model-View-ViewController。它是一個視圖驅動的設計,Controller只是爲了管理View而存在的。蘋果把UIViewController和Model的關係設計交給了咱們本身。因此,如何把一個UIViewController進行更明確的分工,就是這些架構要作的事。

MVP

Model-View-Presenter用一個Presenter,把Controller中View的部分剔除,實現了View和Model的隔絕。各部分分工以下:

  • View負責界面展現和佈局管理,向Presenter暴露視圖更新和數據獲取的接口
  • Presenter負責接收來自View的事件,經過View提供的接口更新視圖,並管理Model
  • Model和MVC中的同樣,提供數據模型

在iOS裏,UIView和UIViewController共同組合成了MVP中的View。UIView負責元素的展現,UIViewController負責界面佈局和組合,並把事件轉發給Presenter。 所以在MVP裏,業務邏輯被放到了Presenter中,由它負責協調View和Model。而因爲View的抽離,Presenter的狀態是可控的,在測試時更不容易受外部影響。

在iOS中使用MVP很簡單,在View和Presenter之間用protocol作好事件傳遞就能夠。缺點就是多了一層用於隔離的接口,會致使代碼數量增大。

可是隨着界面愈來愈複雜,Presenter中的業務代碼也會愈來愈龐大,總有一天會遇到一個新的問題:如何再細分Presenter。

MVVM

Model-View-ViewModel模式,它也和MVP同樣,目的是解決View和Model的耦合。各部分分工以下:

最廣泛的MVVM

  • Model提供數據模型
  • View負責視圖展現
  • ViewModel用於描述View的狀態,例如View的顏色、顯示的文字等屬性類的信息,將View抽象成了一個特殊的模型,而且持有和管理Model,維護業務邏輯

在MVP中,View經過接口的方式來描述本身,在MVVM中,則經過ViewModel來描述本身的特徵。那麼ViewModel如何將本身的變化更新到View上呢?MVVM常常和數據綁定一塊兒出現,在UIViewController中,將View和ViewModel的屬性用相似KVO的方式進行綁定,這樣ViewModel的變化就能當即傳輸到View上。

數據綁定

利用ReactiveCocoa和RxSwift這些函數式響應編程框架實現數據綁定,能夠用不多的代碼完成複雜的業務邏輯,熟練時可以提高開發速度。可是數據綁定的缺點也很明顯:調試困難,數據來源難以回溯,在線上出bug的時候就很難追蹤了,因此從這方面來講又下降了維護的效率。

其實數據綁定只是一種爲了減小膠水代碼的技術實現方式,MVVM的設計並無要求必需要使用數據綁定,你也徹底可使用protocol的方式來將ViewModel的變化傳遞給View,讓數據流向更清晰。MVVM的關鍵是將View進行了抽象,從而實現View和Model的解耦。

ViewModel的職責

可是除了數據綁定,MVVM還有另外一個問題。把業務邏輯放到ViewModel中,雖然可以爲UIViewController減負,可是隻是把問題轉移了,最終ViewModel仍是會變成另外一個Massive ViewModel。

並且當ViewModel維護Model和業務邏輯時,可複用性就會大大下降。例如把同一個登陸界面複用到另外一個app中時,login model中的屬性名或者類型極可能會改變,從而數據處理的方式也會改變,致使ViewModel沒法重用。而當View由多個子View組成時,ViewModel裏也會引入多個子ViewModel,這就又致使了View的實現影響了ViewModel的實現。奇怪的是,國內iOS圈對這個問題的探討十分稀少。

ViewModel究竟是什麼?從它的命名和最初的設計來看,它只是View的抽象,目的是方便和Model進行數據轉換。其實在微軟的WPF和前端裏,MVVM的業務邏輯大部分是放在Model層的,相關的討論能夠參考:

MVVM: ViewModel and Business Logic Connection

Where does business logic sit in MVVM?

The Problems with MVVM on iOS

而針對這個問題,有人又提出了一個MVVMP架構(Model-View-ViewModel-Presenter),把業務邏輯放到了Presenter裏。Presenter的引入讓ViewModel專一於View的抽象,和Model分離開來,只負責管理View相關的狀態、傳遞View的事件,所以ViewModel中的代碼能夠獲得很好的複用。而Presenter負責大部分業務邏輯,若是模塊須要重用,則把業務邏輯中的數據操做邏輯(domain logic)單獨分離出來做爲重用代碼,其餘的沒法重用的應用邏輯(application logic)則依舊放在Presenter裏。

和MVP相比,MVVM用了一種更優雅的方式來抽象View。但它和MVP實際上是相似的,只作了View和Model的解耦,仍然沒有對Controller進行進一步的細分。

那麼如何對Controller進行進一步的職責細分呢?答案就是VIPER。

VIPER

VIPER的全稱是View-Interactor-Presenter-Entity-Router。示意圖以下:

VIPER

相比以前的MVX架構,VIPER多出了兩個東西:Interactor(交互器)和Router(路由)。

各部分職責以下:

View

  • 提供完整的視圖,負責視圖的組合、佈局、更新
  • 向Presenter提供更新視圖的接口
  • 將View相關的事件發送給Presenter

Presenter

  • 接收並處理來自View的事件
  • 向Interactor請求調用業務邏輯
  • 向Interactor提供View中的數據
  • 接收並處理來自Interactor的數據回調事件
  • 通知View進行更新操做
  • 經過Router跳轉到其餘View

Router

  • 提供View之間的跳轉功能,減小了模塊間的耦合
  • 初始化VIPER的各個模塊

Interactor

  • 維護主要的業務邏輯功能,向Presenter提供現有的業務用例
  • 維護、獲取、更新Entity
  • 當有業務相關的事件發生時,處理事件,並通知Presenter

Entity

  • 和Model同樣的數據模型

和MVX的區別

VIPER把MVC中的Controller進一步拆分紅了Presenter、Router和Interactor。和MVP中負責業務邏輯的Presenter不一樣,VIPER的Presenter的主要工做是在View和Interactor之間傳遞事件,並管理一些View的展現邏輯,主要的業務邏輯實現代碼都放在了Interactor裏。Interactor的設計裏提出了"用例"的概念,也就是把每個會出現的業務流程封裝好,這樣可測試性會大大提升。而Router則進一步解決了不一樣模塊之間的耦合。因此,VIPER和上面幾個MVX相比,多總結出了幾個須要維護的東西:

  • View事件管理
  • 數據事件管理
  • 事件和業務的轉化
  • 總結每一個業務用例
  • 模塊內分層隔離
  • 模塊間通訊

而這裏面,還能夠進一步細分一些職責。VIPER實際上已經把Controller的概念淡化了,這拆分出來的幾個部分,都有很明確的單一職責,有些部分之間是徹底隔絕的,在開發時就應該清晰地區分它們各自的職責,而不是將它們視爲一個Controller。

優勢

VIPER的特點就是職責明確,粒度細,隔離關係明確,這樣能帶來不少優勢:

  • 可測試性好。UI測試和業務邏輯測試能夠各自單獨進行。
  • 易於迭代。各部分遵循單一職責,能夠很明確地知道新的代碼應該放在哪裏。
  • 隔離程度高,耦合程度低。一個模塊的代碼不容易影響到另外一個模塊。
  • 易於團隊合做。各部分分工明確,團隊合做時易於統一代碼風格,能夠快速接手別人的代碼。

缺點

  • 一個模塊內的類數量增大,代碼量增大,在層與層之間須要花更多時間設計接口。

使用代碼模板來自動生成文件和模板代碼能夠減小不少重複勞動,而花費時間設計和編寫接口是減小耦合的路上不可避免的,你也可使用數據綁定這樣的技術來減小一些傳遞的層次。

  • 模塊的初始化較爲複雜,打開一個新的界面須要生成View、Presenter、Interactor,而且設置互相之間的依賴關係。而iOS中缺乏這種設置複雜初始化的原生方式。

簡單來講,就是Cocoa框架缺乏一個強大的自定義依賴注入工具。這個問題影響不是特別大,能夠選用一些第三方工具來實現,也能夠在Router的界面跳轉方法裏,對模塊進行初始化,只不過老是不夠完美。針對這個問題,我實現了一個基於protocol聲明依賴的界面跳轉Router,將會在以後的文章中進行詳解。

總結

有人可能會以爲,一個界面模塊真的有必要使用這麼複雜的架構嗎?這樣是否是過分設計?

我反對這種觀點。不要被VIPER的組織圖嚇到,VIPER並不複雜,它是將原來MVC中的Controller中的各類任務進行了清晰的分解,在寫代碼時,你會很清楚你正在作什麼。事實上,它比使用了數據綁定技術的MVVM更加簡單,就是由於它職責明確。從MVC轉到VIPER的過程一樣是很清晰的,它甚至把重構的思路都體現出來了。而MVVM則留下了許多還沒有明確的責任,致使不一樣的人會在某些地方有不一樣的實現。即使你還在使用MVC,你也應該在Controller中分離出VIPER總結出的那些專項職責,既然如此,爲什麼不完全地明確這些職責,把它們分散到不一樣的文件中呢?一旦開始這樣的工做,你就已經向VIPER靠攏了。

有人可能會以爲,VIPER適合大型app,中小型app不必過早使用。

我反對這種觀點。VIPER是單個界面模塊內的架構設計,並非整個app架構層面的設計,和app的總體架構沒有多大的關係,也不存在過早使用VIPER的狀況。因此,嚴格來講,是複雜界面更適合VIPER,而不是大型app更適合VIPER。

至此,個人結論就是,快點擁抱VIPER的懷抱吧。。

開始實踐

VIPER是2013年首次在iOS平臺上提出的設計,十分年輕,所以缺乏大量參與者,以總結出更多最佳實踐。下一篇文章將會從VIPER的源頭開始,比較現有的各類VIPER實現,總結出一個我認爲較好的實施方案。

地址:iOS VIPER架構實踐(二):VIPER詳解與實現。裏面有VIPER的具體Demo和代碼模板。

參考

相關文章
相關標籤/搜索