概述
傻瓜圖解
MVC
一個正統的MVC、三者的任務是什么?
關于View到底該不該寫一些業務代碼
胖Model與瘦Model
強業務、弱業務
胖Model
瘦Model
該用哪個?
MVVM
Model
View
ViewModel
Controller
ReactiveCocoa對于MVVM的意義是什么?
MVCS
MVP
VIPER
關于架構設計、一些觀點
控制好Controller的代碼量
對于MVX如何選擇
無論用哪種模式、都要深刻的理解每個模塊不同的職責
概述
其實只要是架構上的設計、本質上都是三個角色:數據管理者、數據加工者、數據展示者。
不管是MVC、MVVM、MVP、VIPER或者任何新的設計模式、都跳不出這三個角色。無非是把數據管理者的工作進行拆分、唯一的界定標準就是把工作拆分的粒度大小。
而無論哪種思想、最終都逃不開三個問題的取舍。代碼量、通用性、可讀性。
這里我主要寫的是MVC和MVVM、對于其他的架構只是略微提及。
傻瓜圖解
這兩天總有人跟我說我想看Demo...我想了想覺得Demo其實也不太直觀、干脆畫幾個圖好了。
主要是想表達每個模塊里應該放什么類型的東西、線可能有連得不對的地兒。親們盡量意會吧。
MVC
MVC--胖Model
MVC--瘦Model
MVC--胖瘦結合
MVVM
MVP
VIPER
試了試...感覺畫著費勁。能理解了前面幾個的話、看文字應該也能理解VIPER吧。
MVC
MVC就是典型的著重通用型與可讀性、這正是一個作為萬物之初的架構所需要保證的事。簡單、易學。
Model進行數據管理
View進行數據展示
Controller負責根據需求對Model以及View進行調配。
不過和廣義的MVC不同、客戶端(別的我不知道啊、起碼iOS)由于UIViewController自帶一個容器View、所以除了上述的正統任務之外、Controller還需要承擔View的生成、布局等的任務。
一個正統的MVC、三者的任務是什么?
所以、我們可以將MVC三者的任務再進一步細化一下
Model:
為Controller的讀取提供數據
為Controller的寫入提供接口
為Controller提供基本的業務組件
最常用的就是網絡請求之后Json轉Model、寫入數據庫之前Model轉Json。
View:
界面的展示
響應與業務無關的事件(動畫效果、點擊反饋、點擊事件的開關保護等等)
何為業務事件:Model數據的改變、網絡請求的發送、頁面的跳轉、頁面的刷新等等。
Controller
管理self.view的生命周期
負責生成所有的View實例、以及布局。
將恰當的Model交付給View展示。
監聽來自View與業務有關的事件、通過與Model的合作、來完成對應事件的業務。
關于View到底該不該寫一些業務代碼
其實自己以前。也會圖方便、把一些自認為的弱業務寫在View里。
舉個例子:
一個Cell、有用戶Nickname、還有一個用戶頭像的Button。
點擊事件肯定由Cell捕獲。這個時候跳轉用戶主頁的動作、該由View完成、還是傳遞給Controller?
假設我們交由View、也就是當前的Cell跳轉了。很方便、省去了寫代理的小十行代碼。
并且這個Cell也可以挪到其他頁面去使用、一樣能跳到用戶主頁、又省去不少代碼。
有一天、產品讓你在某個頁面點擊頭像不執行任何動作。
咋辦呢?機智如你、給這個Cell添加了一個bool值來控制是否跳轉就好了。
又過了幾天、產品讓你在某個頁面把這個頭像弄成點擊之后彈出舉報框。
這怎么搞呢?也不是不行、你又給這個Cell添了一個枚舉的type。這簡直完美、不同的Type執行不同的事件、你順便取消了那個bool、把他也寫成了一個type。
又過了一陣子、產品又告訴你當滿足某些條件的時候、這個頭像跳主頁。另一些條件的時候、這個頭像不能跳。
于是、你終于寫了個block或者代理、在點擊之后執行一下再看下一步怎么跳轉。
此時、回過頭來再看你的Cell、已經面目全非了、充斥著各種業務判斷。
可能你會說、如果產品真的這么二逼。那我干脆再copy一個Cell就好了啊。
但是別忘了、你當初這么設計這個Cell的時候可是為了節省下頁面跳轉的小十行代碼、而你現在卻要為此付出copy一整個Cell的代價。
其實還有一個更重要的問題、就是你這個View的模塊復用基本為0
假設你需要另起一個新的工程寫一個demo、如果用這個View你首先要解決一大堆跳轉代碼上Controller 文件的缺失、然后還會發現、原來寫的很多邏輯、type在這個demo里毫無用處。挨個刪除、梳理邏輯又要耗費很多時間。
而這些將來會發生的問題、如果你最開始不把業務事件代碼硬寫進View里、一件都不會發生。
胖Model與瘦Model
這里先要引出兩個概念。強業務、弱業務。
二者關鍵的區別是代碼變動的頻率大小與涉及模塊的多少。舉兩個例子:
1、比如把時間戳轉化、小數點的格式化或者修改A屬性進行一系列計算并且改變B屬性、這種業務就屬于弱業務。
2、再比如一個一個訂單Model的確認收貨、就應該歸入沒辦法歸入弱業務、因為涉及網絡請求、加密等等多個底層模塊。
胖Model
主旨是Controller從Model里拿到的數據、不需要進行更多的判斷、處理等操作、就能使用。舉個例子:
Raw Data:? ? timestamp:1234567FatModel:@property(nonatomic,assign)CGFloattimestamp;? ? - (NSString*)ymdDateString;// 2015-04-20 15:16- (NSString*)gapString;// 3分鐘前、1小時前、一天前、2015-3-13 12:34Controller:self.dateLabel.text = [FatModel ymdDateString];self.gapLabel.text = [FatModel gapString];
這就需要將弱業務、寫進Model、很好的滿足的復用的需求。
胖Model也是存在問題的、就是移植的困難。畢竟業務再弱、也是代碼、當項目成長到一定程度、這個Model也將會變得相當的臃腫。
瘦Model
就是要把MVC的M貫徹倒底、除了業務的表達啥都不管。
但是這樣又會導致Controller中的代碼變得異常臃腫(廢話么、連時間戳轉化都要交給Controller不腫才怪)
所以瘦Model要借助一些外來的輔助模塊(索性可以叫Helper)來對弱業務做抽象。舉個例子:
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) {? ? ? ? ...? ? }
該用哪個?
我個人用胖Model用的比較多、但是也借助了一些瘦Model的思想。舉例來講:
除了上述很明確的可以放到Model里的弱業務之外、像一個訂單中的確認收貨、發貨、申請退貨等等操作、他們既不算特別強的業務、而且還有很高的復用需求(訂單列表和訂單詳情都需要確認收貨)。
這種業務有一種特點、就是代碼就在哪里。不管你放到哪、都只能挪不能刪。但挪到哪、都不完全合適。從定義上來講十分莫若兩可。個人覺得:
這種的業務:能不放在Controller里就不要放
你可以干脆放到胖Model里、畢竟將來拆分一個400行的Model、比拆分一個1400行的Controller容易得多。
你也可以單獨新建一個Helper、配合著Model來完成業務。這樣想移植頁面就單用Model、想帶業務移植就帶著Helper(其實這個思路已經很接近MVP了、但是還差提點。MVP還需要為View提供數據)。
MVVM
弱弱的一說、我并不推薦iOS中寫MVVM(因為入侵性實在太強)、不會教你怎么用RAC怎么寫出MVVM、只是想讓你理解什么是MVVM
MVVM現在已經是一種非常成熟的思想了。應用也十分普及、例如Vue以及小程序。
MVVM的初衷也是為了Controller減負。
剛才的胖Model只從Controller移植走了一些簡單的弱業務。
而ViewModel則干脆把數據的處理全部從Controller移植了出去。
理想上相同的輸入(比如網絡服務響應)將會導出相同的輸出(屬性的值)。
簡單的說一下M、V、VM的在架構中所扮演的角色。
Model:
和正統MVC中的瘦Model一樣、只承載最基本的數據單元。
@interfaceUserListModel:NSObject@property(nonatomic,strong,readonly)NSString*userName;@property(nonatomic,strong,readonly)UIImage*portraitImg;@end
View
其實也和正統的MVC一樣、只做展示工作、不承接任何業務邏輯。
但是需要注意的是、有時候也會在View中將ViewModel與View做一些綁定工作(ViewModel本質上也算是Model層、所以View并不適合直接持有ViewModel)。
- (void) awakeFromNib {? ? [superawakeFromNib];? ? RAC(self. portraitImgView,? image) = RACObserve(self,? viewModel. portraitImg);? ? RAC(self. userNameLabel,? text) = RACObserve(self,? viewModel. userName);}
ViewModel
提供了這個頁面展示所有需要的數據的一個對象。
舉一個簡單的例子:
@interfaceUserListViewModel:NSObject@property(nonatomic,assign,readonly)BOOLloading;@property(nonatomic,strong,readonly)NSArray*userList;@property(nonatomic,strong,readwrite)NSString*searchUserName;? - (void) searchUser;- (void) deleteUserWithModel:(UserListModel *)model;- (void) loadMoreUser;
這個ViewModel里涵蓋了所有頁面展示需要的要素。用戶列表、搜索名稱、是否需要顯示網絡加載的小菊花。
并且涵蓋了對這些數據的所有操作方法。加載更多、搜索、刪除。
但是、ViewModel到底也是一個Model層、不應該引入UIKit(View層)。如果刪除需要彈窗、那么這個彈窗動作是不應該交給ViewModel來搞的、因為這已經不屬于數據處理的范疇了
仔細想想、這個ViewModel其實就是把Controller中與頁面相關的數據處理代碼挪進來了而已。
如此、我們設置可以脫離View層。拿著這個ViewModel去跑單元測試。簡直碉堡。
Controller
雖然MVVM中沒有體現出C的字眼、但是實際操作肯定是要遵循著View <-> C <-> ViewModel <-> Model。
起碼在iOS中是、這和Vue中簡單粗暴的方式不同:
//頁面里
{{message}}
? ? ? {{value}}
//js文件里newVue({//數據data:{? ? ? ? ? ? key:'welcome vue',? ? ? ? ? ? arr:['apple','banana','orange','pear'],? ? ? ? ? ? json:{a:'apple',b:'banana',c:'orange'}? ? ? }//方法methods:{? ? ? ? ? ? add:function(){//push 添加元素this.arr.push('tomato');? ? ? ? ? ? }? ? ? }})
因為Html中并沒有明確的Controller的概念、整個Html文件就是Controller容器。
和iOS的區別很明顯:
除去精煉的寫法、整個Html文件所關聯的js資源都可以無障礙互通、所以View層無時無刻不持有著Model層、在View層直接綁定更方便。
所以iOS中Controller的作用就顯而易見了
Controller夾在View和ViewModel之間做的其中一個主要事情就是將View和ViewModel進行綁定。在邏輯上、Controller知道應當展示哪個View、Controller也知道應當使用哪個ViewModel、然而View和ViewModel它們之間是互相不知道的、所以Controller就負責控制他們的綁定關系。
ReactiveCocoa對于MVVM的意義是什么?
ReactiveCocoa并不是MVVM思想的根本、不用ReactiveCocoa也能MVVM、用ReactiveCocoa能更好地體現MVVM的精髓。
我一直強調MVC中的M與V是應該盡量不要互相持有的。
這個時候如何把原本松散的二者通過C緊密的聯系起來、就要進行數據綁定。
而這種數據綁定、iOS本身并沒有什么太靠譜的辦法(就像剛才前端例子中的
{{message}}
、這種寫法)。
雖然KVO、Notification、block、delegate和target-action都可以用來做數據通信進而實現綁定
但都不如ReactiveCocoa來的《《《優雅》》》。
對、這就是我開始說為什么RAC對于MVVM不是必須的。
如果不用ReactiveCocoa、綁定關系可能就做不到那么松散那么好、但并不影響它還是MVVM。
MVCS
將數據持久化的代碼移植給了store...
MVP
實際上就是將Controller中關于Model與View的調配處理的代碼移植了過來。各部分分工如下:
View
負責界面展示和布局管理、向Presenter暴露視圖更新和數據獲取的接口
Presenter
負責接收來自View的事件、通過View提供的接口更新視圖,并管理Model。
Model
和MVC中的一樣,提供數據模型
VIPER
除了View沒拆、其它的都拆了....
在MVP的基礎上新增了Interactor與Router
View
提供完整的視圖。負責視圖的組合、布局、更新
向Presenter提供更新視圖的接口
將View相關的事件發送給Presenter
Interactor
維護主要的業務邏輯功能,向Presenter提供現有的業務用例
維護、獲取、更新Entity
當有業務相關的事件發生時、處理事件、并通知Presenter
Presenter
接收并處理來自View的事件
向Interactor請求調用業務邏輯
向Interactor提供View中的數據
接收并處理來自Interactor的數據回調事件
通知View進行更新操作
通過Router跳轉到其他View
Entity
和Model一樣的數據模型
Router
提供View之間的跳轉功能、減少了模塊間的耦合
初始化VIPER的各個模塊
VIPER與其他的架構相比最大的優勢就是粒度簡直細化成了塵埃、極大的提高了可測性。
但問題也相當顯著、層級越多、數據傳遞的工作量(API)就越大。文件越多、新建一個頁面的成本也就越高。
關于架構設計、一些觀點
除了MVVM、它對iOS的入侵性簡直太高、主要取決于團隊的決策(比如是不是新項目、Leader是不是想玩玩看)。
控制好Controller的代碼量
隨著項目的進行、代碼量最多只能優化、膨脹不可避免。
而在沒辦法繼續精簡的前提下、想控制Controller的代碼量。就要在可讀性和通用性之間進行取舍。該挪走的時候就挪走吧、畢竟梳理一個單獨的模塊、比梳理一個幾千行的Controller要方便多了。
對于MVX如何選擇
其實完全要看業務的性質以及復雜度。
如果你一個頁面只有一個UITableView、搞出一些奇淫技巧其實意義? 不大、徒增煩惱。踏踏實實用MVC對大家都好。
如果感覺業務里有非常多的View與Model互通、或者需大量復用、可以用MVP。
如果有大量的數據讀寫、可以用MVCS。
如果業務相當的復雜、耦合讓人渾身難受。做好模塊化或者干脆VIPER才是出路。
無論用哪種模式、都要深刻的理解每個模塊不同的職責
比如MVVM里的VM、既然是Model層、就不要把UIKit放進去。
再比如MVP中的P、既然是為了幫助Controller協調M與V而生、就不要把與二者無關的工作也搶過來干。