談起iOS架構, 也許你直接想到的就是MVC,MVVM等等. 其實這是不准確的, iOS架構包含的內容有很多, 而上面的這些只是iOS架構中的架構模式.
那麼你在進行架構設計的時候到底是選擇MVC或者MVVM還是選擇VIPER呢? 這就要根據你的業務需求, 項目規模以及項目未來的推演預測等諸多因素了.
這也是爭議頗多的話題,所以我在這裡來把幾個主流思想做一個梳理, 今天就來說一說 ———– iOS架構之架構模式.
假如你不關心架構模式, 那麼總有一天,你會處在同一個龐大的類中. 你會發現在這樣的條件下, 調試某些功能或者尋找某個bug是多麼的不易, 你會花費大量的時間和精力放在這些臃腫的代碼中. 所以, 你應該提前想到這些東西, 根據項目的實際情況選擇一個合適的架構模式原來是如此重要.
一般的, 模塊角色會有三種:數據管理者、數據加工者、數據展示者. 面對這些各種各樣的架構模式思想,不外乎就是制訂了一個規范,規定了這三個角色應當如何進行數據交換。但一個號的架構的必要特點是具備如下特點:
各角色任務均衡, 分工明確 測試可行性高或者說易於測試 維護成本低 易用性好MVC(Model-View-Controller)是最老牌的的思想,其中Model就是作為數據管理者,View作為數據展示者,Controller作為數據加工者,Model和View又都是由Controller來根據業務需求調配,所以Controller還負擔了一個數據流調配的功能。
模型(Model)的分工:
- 為ViewController提供數據
- 為ViewController存儲數據提供接口
- 提供經過抽象的業務基本組件,供Controller調度
控制器(Controller)的分工:
- 管理View Container的生命周期
- 負責生成所有的View實例,並放入View Container
- 監聽來自View與業務有關的事件,通過與Model的合作,來完成對應事件的業務。
視圖(View)的分工:
- 響應與業務無關的事件,並因此引發動畫效果,點擊反饋(如果合適的話,盡量還是放在View去做)等。
- 界面元素表達
分工總結:
視圖(View):用戶界面
控制器(Controller):業務邏輯及處理
模型(Model):數據存儲
1、Model和View永遠不能相互通信,只能通過Controller傳遞。
2、Controller可以直接與Model通信(讀寫調用Model),Model通過Notification和KVO機制與Controller間接通信。
3、Controller與View通過Target/Action, delegate和datasource三種模式進行通信。通過這三種模式,View就可以向Controller通信, Action/Target 模式來讓Controller 監聽View 觸發的事件。View又通過Data source和delegate進行數據獲取和某些通信操作。
蘋果自身就采用的是這種架構思路,從名字也能看出,也是基於MVC衍生出來的一套架構。從概念上來說,它拆分的部分是Model部分,拆出來一個Store。這個Store專門負責數據存取。但從實際操作的角度上講,它拆開的是Controller。
這算是瘦Model的一種方案,瘦Model只是專門用於表達數據,然後存儲、數據處理都交給外面的來做。MVCS使用的前提是,它假設了你是瘦Model,同時數據的存儲和處理都在Controller去做。所以對應到MVCS,它在一開始就是拆分的Controller。因為Controller做了數據存儲的事情,就會變得非常龐大,那麼就把Controller專門負責存取數據的那部分抽離出來,交給另一個對象去做,這個對象就是Store。這麼調整之後,整個結構也就變成了真正意義上的MVCS。
分工總結:
視圖(View):用戶界面
控制器(Controller):業務邏輯及處理
模型(Model):數據存儲
存儲器(Store):數據處理邏輯
MVCS是基於瘦Model的一種架構思路,把原本Model要做的很多事情中的其中一部分關於數據存儲的代碼抽象成了Store,在一定程度上降低了Controller的壓力。
胖Model包含了部分弱業務邏輯。胖Model要達到的目的是,Controller從胖Model這裡拿到數據之後,不用額外做操作或者只要做非常少的操作,就能夠將數據直接應用在View上。
FatModel做了這些弱業務之後,Controller就能變得非常skinny,Controller只需要關注強業務代碼就行了。眾所周知,強業務變動的可能性要比弱業務大得多,弱業務相對穩定,所以弱業務塞進Model裡面是沒問題的。另一方面,弱業務重復出現的頻率要大於強業務,對復用性的要求更高,如果這部分業務寫在Controller,類似的代碼會灑得到處都是,一旦弱業務有修改(弱業務修改頻率低不代表就沒有修改),這個事情就是一個災難。如果塞到Model裡面去,改一處很多地方就能跟著改,就能避免這場災難。
然而其缺點就在於,胖Model相對比較難移植,雖然只是包含弱業務,但好歹也是業務,遷移的時候很容易拔出蘿卜帶出泥。另外一點,MVC的架構思想更加傾向於Model是一個Layer,而不是一個Object,不應該把一個Layer應該做的事情交給一個Object去做。最後一點,軟件是會成長的,FatModel很有可能隨著軟件的成長越來越Fat,最終難以維護。
瘦Model只負責業務數據的表達,所有業務無論強弱一律扔到Controller。瘦Model要達到的目的是,盡一切可能去編寫細粒度Model,然後配套各種helper類或方法來對弱業務做抽象,強業務依舊交給Controller。
由於SlimModel跟業務完全無關,它的數據可以交給任何一個能處理它數據的Helper或其他的對象,來完成業務。在代碼遷移的時候獨立性很強,很少會出現拔出蘿卜帶出泥的情況。另外,由於SlimModel只是數據表達,對它進行維護基本上是0成本,軟件膨脹得再厲害,SlimModel也不會大到哪兒去。
缺點就在於,Helper這種做法也不見得很好,這裡有一篇文章批判了這個事情。另外,由於Model的操作會出現在各種地方,SlimModel在一定程度上違背了DRY(Don’t Repeat Yourself)的思路,Controller仍然不可避免在一定程度上出現代碼膨脹。
這看起來不正是蘋果所提出的MVC方案嗎?確實是的,這種模式的名字叫做MVC,但是,這就是說蘋果的MVC實際上就是MVP了?不,並不是這樣的。如果你仔細回憶一下,View是和Controller緊密耦合的,但是MVP的協調器Presenter並沒有對ViewController的生命周期做任何改變,因此View可以很容易的被模擬出來。在Presenter中根本沒有和布局有關的代碼,但是它卻負責更新View的數據和狀態。
MVP是第一個如何協調整合三個實際上分離的層次的架構模式,既然我們不希望View涉及到Model,那麼在顯示的View Controller(其實就是View)中處理這種協調的邏輯就是不正確的,因此我們需要在其他地方來做這些事情, 比如用戶輸入操作, 數據請求, 數據處理等等業務邏輯。
分工總結:
視圖(View):用戶界面
模型(Model):數據存儲
展示器(Presenter):數據處理, 業務邏輯。
View和Presenter之間是完全解耦的,他們通過接口來交互
View和Presenter是一對一關系,意味著一個Presenter只映射一個View, 且他們之間是可以雙向交互的。
MVVM 是 MVC 模式的一種演進,它主要解決了 ViewController 過於臃腫帶來的不易維護和測試的問題。其中 ViewModel 的主要職責是處理業務邏輯並提供 View 所需的數據,這樣 VC 就不用關心業務,自然也就瘦了下來。ViewModel 只關心業務數據不關心 View,所以不會與 View 產生耦合,也就更方便進行單元測試。
View 是一個殼,它所呈現的內容都需要由 ViewModel 來提供,而 View 又不與 ViewModel 直接溝通,這時就需要 ViewController 來做中間的協調者。
ViewController 持有 View 和 ViewModel,當 VC 初始化時,會讓 ViewModel 去取數據,簡單來說就是調用 VM 的某個獲取數據的方法。
但大部分國內外資料闡述MVVM的時候都是這樣排布的:View <-> ViewModel <-> Model,造成了MVVM不需要Controller的錯覺,現在似乎發展成業界開始出現MVVM是不需要Controller的的聲音了。其實MVVM是一定需要Controller的參與的,雖然MVVM在一定程度上弱化了Controller的存在感,並且給Controller做了減負瘦身(這也是MVVM的主要目的)。但是,這並不代表MVVM中不需要Controller,MMVC和MVVM他們之間的關系應該是這樣:
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。
其實你可以發現MVVM 與 MVP在業務邏輯上非常相似.一般情況下安卓用MVP要多一些, iOS用MVVM要多一些.
分工總結:
視圖(View):用戶界面
模型(Model):數據存儲
ViewModel:數據請求, 數據處理, 業務邏輯。
VIPER 是一個創建 iOS 應用簡明構架的程序。VIPER 可以是視圖 (View),交互器 (Interactor),展示器 (Presenter),實體 (Entity) 以及路由 (Router) 的首字母縮寫。簡明架構將一個應用程序的邏輯結構劃分為不同的責任層。這使得它更容易隔離依賴項 (如數據庫),也更容易測試各層間的邊界處的交互。
當我們把VIPER和MV(X)系列作比較時,我們會在任務均攤性方面發現一些不同:
Model 邏輯通過把實體作為最小的數據結構轉換到交互器中。
Controller/Presenter/ViewModel的UI展示方面的職責移到了Presenter中,但是並沒有數據轉換相關的操作。
VIPER是第一個通過路由器實現明確的地址導航模式。
視圖(View):根據展示器的要求顯示界面,並將用戶輸入反饋給展示器。
交互器(Interactor):包含由用例指定的業務邏輯。
展示器(Presenter):包含為顯示(從交互器接受的內容)做的准備工作的相關視圖邏輯,並對用戶輸入進行反饋(從交互器獲取新數據)。
實體(Entity):包含交互器要使用的基本模型對象。
路由(Router):包含用來描述屏幕顯示和顯示順序的導航邏輯。
交互器在應用中代表著一個獨立的用例。它具有業務邏輯以操縱模型對象(實體)執行特定的任務。交互器中的工作應當獨立與任何用戶界面,
由於交互器是一個 PONSO (Plain Old NSObject,普通的 NSObject),它主要包含了邏輯,因此很容易使用 TDD 進行開發。
實體是被交互器操作的模型對象,並且它們只被交互器所操作。交互器永遠不會傳輸實體至表現層 (比如說展示器)。
實體也應該是 PONSOs。如果你使用 Core Data,最好是將托管對象保持在你的數據層之後,交互器不應與 NSManageObjects 協同工作。
展示器是一個主要包含了驅動用戶界面的邏輯的 PONSO,它總是知道何時呈現用戶界面。基於其收集來自用戶交互的輸入功能,它可以在合適的時候更新用戶界面並向交互器發送請求。
視圖一般是被動的,它通常等待展示器下發需要顯示的內容,而不會向其索取數據。視圖(例如登錄界面的登錄視圖控件)所定義的方法應該允許展示器在高度抽象的層次與之交流。展示器通過內容進行表達,而不關心那些內容所顯示的樣子。展示器不知道 UILabel,UIButton 等的存在,它只知道其中包含的內容以及何時需要顯示。內容如何被顯示是由視圖來進行控制的。
視圖是一個抽象的接口 (Interface),在 Objective-C 中使用協議被定義。一個 UIViewController 或者它的一個子類會實現視圖協議。
屏幕間的路徑會在交互設計師創建的線框 (wireframes) 裡進行定義。在 VIPER 中,路由是由兩個部分來負責的:展示器和線框。一個線框對象包括 UIWindow,UINavigationController,UIViewController 等部分,它負責創建視圖/視圖控制器並將其裝配到窗口中。
由於展示器包含了響應用戶輸入的邏輯,因此它就擁有知曉何時導航至另一個屏幕以及具體是哪一個屏幕的能力。而同時,線框知道如何進行導航。在兩者結合起來的情況下,展示器可以使用線框來進行實現導航功能,它們兩者一起描述了從一個屏幕至另一個屏幕的路由過程。
數據存儲模塊負責提供實體給交互器。因為交互器要完成業務邏輯,因此它需要從數據存儲中獲取實體並操縱它們,然後將更新後的實體再放回數據存儲中。數據存儲管理實體的持久化,而實體應該對數據庫全然不知,正因如此,實體並不知道如何對自己進行持久化。
交互器同樣不需要知道如何將實體持久化,有時交互器更希望使用一個 data manager 來使其與數據存儲的交互變得容易。Data manager 可以處理更多的針對存儲的操作,比如創建獲取請求,構建查詢等等。這就使交互器能夠將更多的注意力放在應用邏輯上,而不必再了解實體是如何被聚集或持久化的。
在 iOS 的項目中使用 Core Data 經常比構架本身還容易引起更多爭議。然而,利用 VIPER 來使用 Core Data 將給你帶來使用 Core Data 的前所未有的良好體驗。在持久化數據的工具層面上,Core Data 可以保持快速存取和低內存占用方面,簡直是個神器。但是有個很惱人的地方,它會像觸須一樣把 NSManagedObjectContext 延伸至你所有的應用實現文件中,特別是那些它們不該待的地方。VIPER 可以使 Core Data 待在正確的地方:數據存儲層。
在待辦事項示例中,應用僅有的兩部分知道使用了 Core Data,其一是數據存儲本身,它負責建立 Core Data 堆棧;另一個是 data manager。Data manager 執行了獲取請求,將數據存儲返回的 NSManagedObject 對象轉換為標准的 PONSO 模型對象,並傳輸回業務邏輯層。這樣一來,應用程序核心將不再依賴於 Core Data,附加得到的好處是,你也再也不用擔心過期數據 (stale) 和沒有良好組織的多線程 NSManagedObjects 來糟蹋你的工作成果了。
這些架構模式還是要根據你的項目需求, 項目規模等條件來進行選擇。項目規模越小, 越簡單的話, 就盡量使用最基本的MVC, 項目再復雜一些的話, 可以選擇使用MVP, MVVM, 更加繁瑣的項目的話, 那VIPER就可以排上用場了。你會發現, 這個順序其實是由簡至繁的, 而為什麼要做這樣的選擇呢? 因為他們都是遵循單一責任原則的, 當簡單的項目繁重後, 盡量開辟出新的角色, 將其工作任務單一化, 這樣就可以達到項目思路清晰, 易於測試, 易用性高, 維護成本低等要求了。
其實, 你會發現其實這些架構模式都是可以從MVC的模式下拆分出來的。 我個人認為, 在做具體的架構設計時,不需要拘泥於MVC、MVVM、VIPER等死規矩, 也可以自己做一些小的改變, 但要記住只能拆分其它不重要的任務, 而且拆分後的模塊要盡可能提高可復用性和抽象度。