目錄
- 概述
-
傻瓜圖解
- MVC
- 一個(gè)正統(tǒng)的MVC、三者的任務(wù)是什么?
- 關(guān)于View到底該不該寫(xiě)一些業(yè)務(wù)代碼
- 胖Model與瘦Model
- 強(qiáng)業(yè)務(wù)谚鄙、弱業(yè)務(wù)
- 胖Model
- 瘦Model
- 該用哪個(gè)各拷?
- MVVM
- Model
- View
- ViewModel
- Controller
- ReactiveCocoa對(duì)于MVVM的意義是什么?
- MVCS
- MVP
- VIPER
- 關(guān)于架構(gòu)設(shè)計(jì)闷营、一些觀點(diǎn)
- 控制好Controller的代碼量
- 對(duì)于MVX如何選擇
- 無(wú)論用哪種模式烤黍、都要深刻的理解每個(gè)模塊不同的職責(zé)
概述
其實(shí)只要是架構(gòu)上的設(shè)計(jì)、本質(zhì)上都是三個(gè)角色:數(shù)據(jù)管理者
傻盟、數(shù)據(jù)加工者
速蕊、數(shù)據(jù)展示者
。
不管是MVC娘赴、MVVM规哲、MVP、VIPER或者任何新的設(shè)計(jì)模式诽表、都跳不出這三個(gè)角色唉锌。無(wú)非是把數(shù)據(jù)管理者
的工作進(jìn)行拆分隅肥、唯一的界定標(biāo)準(zhǔn)就是把工作拆分的粒度大小。
而無(wú)論哪種思想袄简、最終都逃不開(kāi)三個(gè)問(wèn)題的取舍腥放。代碼量
、通用性
绿语、可讀性
秃症。
這里我主要寫(xiě)的是MVC和MVVM、對(duì)于其他的架構(gòu)只是略微提及吕粹。
傻瓜圖解
這兩天總有人跟我說(shuō)我想看Demo...我想了想覺(jué)得Demo其實(shí)也不太直觀伍纫、干脆畫(huà)幾個(gè)圖好了。
主要是想表達(dá)每個(gè)模塊里應(yīng)該放什么類型的東西昂芜、線可能有連得不對(duì)的地兒。親們盡量意會(huì)吧赔蒲。
-
MVC
-
MVC--胖Model
-
MVC--瘦Model
-
MVC--胖瘦結(jié)合
-
MVVM
-
MVP
-
VIPER
試了試...感覺(jué)畫(huà)著費(fèi)勁泌神。能理解了前面幾個(gè)的話、看文字應(yīng)該也能理解VIPER吧舞虱。
MVC
MVC就是典型的著重通用型與可讀性欢际、這正是一個(gè)作為萬(wàn)物之初的架構(gòu)所需要保證的事。簡(jiǎn)單矾兜、易學(xué)损趋。
-
Model
進(jìn)行數(shù)據(jù)管理 -
View
進(jìn)行數(shù)據(jù)展示 -
Controller
負(fù)責(zé)根據(jù)需求對(duì)Model
以及View
進(jìn)行調(diào)配。
不過(guò)和廣義的MVC不同椅寺、客戶端(別的我不知道啊浑槽、起碼iOS)由于UIViewController
自帶一個(gè)容器View
、所以除了上述的正統(tǒng)任務(wù)之外返帕、Controller
還需要承擔(dān)View的生成桐玻、布局等的任務(wù)。
一個(gè)正統(tǒng)的MVC荆萤、三者的任務(wù)是什么?
所以镊靴、我們可以將MVC三者的任務(wù)再進(jìn)一步細(xì)化一下
-
Model:
- 為
Controller
的讀取提供數(shù)據(jù) - 為
Controller
的寫(xiě)入提供接口 - 為
Controller
提供基本的業(yè)務(wù)組件
- 為
最常用的就是網(wǎng)絡(luò)請(qǐng)求之后Json轉(zhuǎn)Model、寫(xiě)入數(shù)據(jù)庫(kù)之前Model轉(zhuǎn)Json链韭。
-
View:
- 界面的展示
- 響應(yīng)與業(yè)務(wù)無(wú)關(guān)的事件(動(dòng)畫(huà)效果偏竟、點(diǎn)擊反饋、點(diǎn)擊事件的開(kāi)關(guān)保護(hù)等等)
何為業(yè)務(wù)事件:Model
數(shù)據(jù)的改變敞峭、網(wǎng)絡(luò)請(qǐng)求的發(fā)送踊谋、頁(yè)面的跳轉(zhuǎn)、頁(yè)面的刷新等等旋讹。
-
Controller
- 管理
self.view
的生命周期 - 負(fù)責(zé)生成所有的View實(shí)例褪子、以及布局量淌。
- 將恰當(dāng)?shù)?code>Model交付給
View
展示。 - 監(jiān)聽(tīng)來(lái)自
View
與業(yè)務(wù)有關(guān)的事件嫌褪、通過(guò)與Model
的合作呀枢、來(lái)完成對(duì)應(yīng)事件的業(yè)務(wù)。
- 管理
-
關(guān)于View到底該不該寫(xiě)一些業(yè)務(wù)代碼
其實(shí)自己以前笼痛。也會(huì)圖方便裙秋、把一些自認(rèn)為的弱業(yè)務(wù)
寫(xiě)在View
里。
舉個(gè)例子:
- 一個(gè)Cell缨伊、有用戶Nickname摘刑、還有一個(gè)用戶頭像的Button。
點(diǎn)擊事件肯定由Cell捕獲刻坊。這個(gè)時(shí)候跳轉(zhuǎn)用戶主頁(yè)的動(dòng)作枷恕、該由
View
完成、還是傳遞給Controller
谭胚?
假設(shè)我們交由View
徐块、也就是當(dāng)前的Cell跳轉(zhuǎn)了。很方便灾而、省去了寫(xiě)代理的小十行代碼胡控。
并且這個(gè)Cell也可以挪到其他頁(yè)面去使用、一樣能跳到用戶主頁(yè)旁趟、又省去不少代碼昼激。
- 有一天、產(chǎn)品讓你在某個(gè)頁(yè)面點(diǎn)擊頭像不執(zhí)行任何動(dòng)作锡搜。
咋辦呢橙困?機(jī)智如你、給這個(gè)Cell添加了一個(gè)
bool
值來(lái)控制是否跳轉(zhuǎn)就好了耕餐。
- 又過(guò)了幾天纷宇、產(chǎn)品讓你在某個(gè)頁(yè)面把這個(gè)頭像弄成點(diǎn)擊之后彈出舉報(bào)框。
這怎么搞呢蛾方?也不是不行像捶、你又給這個(gè)Cell添了一個(gè)枚舉的
type
。這簡(jiǎn)直完美桩砰、不同的Type執(zhí)行不同的事件拓春、你順便取消了那個(gè)bool
、把他也寫(xiě)成了一個(gè)type
亚隅。
- 又過(guò)了一陣子硼莽、產(chǎn)品又告訴你當(dāng)滿足某些條件的時(shí)候、這個(gè)頭像跳主頁(yè)。另一些條件的時(shí)候懂鸵、這個(gè)頭像不能跳偏螺。
于是、你終于寫(xiě)了個(gè)block或者代理匆光、在點(diǎn)擊之后執(zhí)行一下再看下一步怎么跳轉(zhuǎn)套像。
此時(shí)、回過(guò)頭來(lái)再看你的Cell
终息、已經(jīng)面目全非了夺巩、充斥著各種業(yè)務(wù)判斷。可能你會(huì)說(shuō)周崭、如果產(chǎn)品真的這么二逼柳譬。那我干脆再copy一個(gè)
Cell
就好了啊。但是別忘了续镇、你當(dāng)初這么設(shè)計(jì)這個(gè)
Cell
的時(shí)候可是為了節(jié)省下頁(yè)面跳轉(zhuǎn)的小十行代碼美澳、而你現(xiàn)在卻要為此付出copy一整個(gè)Cell
的代價(jià)。其實(shí)還有一個(gè)更重要的問(wèn)題摸航、就是你這個(gè)
View
的模塊復(fù)用基本為0假設(shè)你需要另起一個(gè)新的工程寫(xiě)一個(gè)demo制跟、如果用這個(gè)
View
你首先要解決一大堆跳轉(zhuǎn)代碼上Controller 文件
的缺失、然后還會(huì)發(fā)現(xiàn)忙厌、原來(lái)寫(xiě)的很多邏輯、type
在這個(gè)demo里毫無(wú)用處江咳。挨個(gè)刪除逢净、梳理邏輯又要耗費(fèi)很多時(shí)間。而這些將來(lái)會(huì)發(fā)生的問(wèn)題歼指、如果你最開(kāi)始不把業(yè)務(wù)事件代碼硬寫(xiě)進(jìn)
View
里爹土、一件都不會(huì)發(fā)生。
胖Model與瘦Model
這里先要引出兩個(gè)概念踩身。強(qiáng)業(yè)務(wù)
胀茵、弱業(yè)務(wù)
。
二者關(guān)鍵的區(qū)別是代碼變動(dòng)的頻率大小與涉及模塊的多少挟阻。舉兩個(gè)例子:
1琼娘、比如把時(shí)間戳轉(zhuǎn)化
、小數(shù)點(diǎn)的格式化
或者修改A屬性進(jìn)行一系列計(jì)算
并且改變B屬性附鸽、這種業(yè)務(wù)就屬于弱業(yè)務(wù)脱拼。
2、再比如一個(gè)一個(gè)訂單Model的確認(rèn)收貨坷备、就應(yīng)該歸入沒(méi)辦法歸入弱業(yè)務(wù)熄浓、因?yàn)樯婕熬W(wǎng)絡(luò)請(qǐng)求、加密等等多個(gè)底層模塊省撑。
-
胖Model
主旨是Controlle
r從Model
里拿到的數(shù)據(jù)赌蔑、不需要進(jìn)行更多的判斷俯在、處理等操作、就能使用娃惯。舉個(gè)例子:
Raw Data:
timestamp:1234567
FatModel:
@property (nonatomic, assign) CGFloat timestamp;
- (NSString *)ymdDateString; // 2015-04-20 15:16
- (NSString *)gapString; // 3分鐘前跷乐、1小時(shí)前、一天前石景、2015-3-13 12:34
Controller:
self.dateLabel.text = [FatModel ymdDateString];
self.gapLabel.text = [FatModel gapString];
這就需要將弱業(yè)務(wù)
劈猿、寫(xiě)進(jìn)Model
、很好的滿足的復(fù)用的需求潮孽。
胖Model也是存在問(wèn)題的揪荣、就是移植的困難。畢竟業(yè)務(wù)再弱往史、也是代碼仗颈、當(dāng)項(xiàng)目成長(zhǎng)到一定程度、這個(gè)Model也將會(huì)變得相當(dāng)?shù)挠纺[椎例。
-
瘦Model
就是要把MVC的M貫徹倒底挨决、除了業(yè)務(wù)的表達(dá)啥都不管。
但是這樣又會(huì)導(dǎo)致Controller中的代碼變得異常臃腫(廢話么订歪、連時(shí)間戳轉(zhuǎn)化都要交給Controller不腫才怪)
所以瘦Model要借助一些外來(lái)的輔助模塊(索性可以叫Helper
)來(lái)對(duì)弱業(yè)務(wù)做抽象脖祈。舉個(gè)例子:
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) {
...
}
-
該用哪個(gè)?
我個(gè)人用胖Model用的比較多刷晋、但是也借助了一些瘦Model的思想盖高。舉例來(lái)講:
除了上述很明確的可以放到Model里的弱業(yè)務(wù)之外、像一個(gè)訂單中的確認(rèn)收貨眼虱、發(fā)貨喻奥、申請(qǐng)退貨等等操作、他們既不算特別強(qiáng)的業(yè)務(wù)
捏悬、而且還有很高的復(fù)用需求
(訂單列表和訂單詳情都需要確認(rèn)收貨)撞蚕。
這種業(yè)務(wù)有一種特點(diǎn)、就是代碼就在哪里过牙。不管你放到哪甥厦、都只能挪不能刪。但挪到哪寇钉、都不完全合適矫渔。從定義上來(lái)講十分莫若兩可
。個(gè)人覺(jué)得:
- 這種的業(yè)務(wù):能不放在Controller里就不要放
- 你可以干脆放到胖Model里摧莽、畢竟將來(lái)拆分一個(gè)400行的Model庙洼、比拆分一個(gè)1400行的Controller容易得多。
- 你也可以單獨(dú)新建一個(gè)Helper、配合著Model來(lái)完成業(yè)務(wù)油够。這樣想移植頁(yè)面就單用Model蚁袭、想帶業(yè)務(wù)移植就帶著Helper(
其實(shí)這個(gè)思路已經(jīng)很接近MVP了、但是還差提點(diǎn)石咬。MVP還需要為View提供數(shù)據(jù)
)揩悄。
MVVM
弱弱的一說(shuō)、我并不推薦iOS中寫(xiě)MVVM(因?yàn)槿肭中詫?shí)在太強(qiáng))鬼悠、不會(huì)教你怎么用RAC怎么寫(xiě)出MVVM删性、只是想讓你理解什么是MVVM
MVVM現(xiàn)在已經(jīng)是一種非常成熟的思想了。應(yīng)用也十分普及焕窝、例如Vue以及小程序蹬挺。
MVVM的初衷也是為了Controller
減負(fù)。
剛才的胖Model
只從Controller
移植走了一些簡(jiǎn)單的弱業(yè)務(wù)它掂。
而ViewModel
則干脆把數(shù)據(jù)的處理全部從Controller
移植了出去巴帮。
理想上相同的輸入(比如網(wǎng)絡(luò)服務(wù)響應(yīng))將會(huì)導(dǎo)出相同的輸出(屬性的值)。
簡(jiǎn)單的說(shuō)一下M虐秋、V榕茧、VM的在架構(gòu)中所扮演的角色。
-
Model
:
和正統(tǒng)MVC中的瘦Model
一樣客给、只承載最基本的數(shù)據(jù)單元用押。
@interface UserListModel: NSObject
@property (nonatomic, strong, readonly) NSString *userName;
@property (nonatomic, strong, readonly) UIImage *portraitImg;
@end
View
其實(shí)也和正統(tǒng)的MVC一樣、只做展示工作靶剑、不承接任何業(yè)務(wù)邏輯蜻拨。
但是需要注意的是、有時(shí)候也會(huì)在View中將ViewModel
與View
做一些綁定工作(ViewModel
本質(zhì)上也算是Model層
抬虽、所以View
并不適合直接持有ViewModel
)官觅。
- (void) awakeFromNib {
[super awakeFromNib];
RAC(self. portraitImgView, image) = RACObserve(self, viewModel. portraitImg);
RAC(self. userNameLabel, text) = RACObserve(self, viewModel. userName);
}
ViewModel
提供了這個(gè)頁(yè)面展示所有需要的數(shù)據(jù)的一個(gè)對(duì)象纵菌。
舉一個(gè)簡(jiǎn)單的例子:
@interface UserListViewModel: NSObject
@property (nonatomic, assign, readonly) BOOL loading;
@property (nonatomic, strong, readonly) NSArray <UserListModel *>*userList;
@property (nonatomic, strong, readwrite) NSString *searchUserName;
- (void) searchUser;
- (void) deleteUserWithModel:(UserListModel *)model;
- (void) loadMoreUser;
這個(gè)ViewModel
里涵蓋了所有頁(yè)面展示需要的要素阐污。用戶列表、搜索名稱咱圆、是否需要顯示網(wǎng)絡(luò)加載的小菊花笛辟。
并且涵蓋了對(duì)這些數(shù)據(jù)的所有操作方法。加載更多序苏、搜索手幢、刪除。
但是忱详、ViewModel
到底也是一個(gè)Model層
围来、不應(yīng)該引入U(xiǎn)IKit(View層
)。如果刪除需要彈窗、那么這個(gè)彈窗動(dòng)作是不應(yīng)該交給ViewModel來(lái)搞的监透、因?yàn)檫@已經(jīng)不屬于數(shù)據(jù)處理的范疇了
仔細(xì)想想桶错、這個(gè)ViewModel
其實(shí)就是把Controller
中與頁(yè)面相關(guān)的數(shù)據(jù)處理代碼挪進(jìn)來(lái)了而已。
如此胀蛮、我們?cè)O(shè)置可以脫離View
層院刁。拿著這個(gè)ViewModel
去跑單元測(cè)試。簡(jiǎn)直碉堡粪狼。
-
Controller
雖然MVVM中沒(méi)有體現(xiàn)出C的字眼退腥、但是實(shí)際操作肯定是要遵循著View <-> C <-> ViewModel <-> Model
。
起碼在iOS中是再榄、這和Vue中簡(jiǎn)單粗暴的方式不同:
//頁(yè)面里
<p>{{message}}</p>
<li v-for="value in arr">
{{value}}
</li>
//js文件里
new Vue({
//數(shù)據(jù)
data:{
key:'welcome vue',
arr:['apple','banana','orange','pear'],
json:{a:'apple',b:'banana',c:'orange'}
}
//方法
methods:{
add:function(){
//push 添加元素
this.arr.push('tomato');
}
}
})
因?yàn)镠tml中并沒(méi)有明確的Controller
的概念狡刘、整個(gè)Html文件就是Controller
容器。
和iOS的區(qū)別很明顯:
除去精煉的寫(xiě)法不跟、整個(gè)Html文件所關(guān)聯(lián)的js資源都可以無(wú)障礙互通颓帝、所以View
層無(wú)時(shí)無(wú)刻不持有著Model
層、在View層直接綁定更方便窝革。
所以iOS中
Controller
的作用就顯而易見(jiàn)了
Controller
夾在View
和ViewModel
之間做的其中一個(gè)主要事情就是將View
和ViewModel
進(jìn)行綁定购城。在邏輯上、Controller
知道應(yīng)當(dāng)展示哪個(gè)View
虐译、Controller
也知道應(yīng)當(dāng)使用哪個(gè)ViewModel
瘪板、然而View
和ViewModel
它們之間是互相不知道的、所以Controller
就負(fù)責(zé)控制他們的綁定關(guān)系漆诽。-
ReactiveCocoa對(duì)于MVVM的意義是什么侮攀?
ReactiveCocoa
并不是MVVM思想的根本、不用ReactiveCocoa
也能MVVM厢拭、用ReactiveCocoa
能更好地體現(xiàn)MVVM的精髓兰英。
我一直強(qiáng)調(diào)MVC中的M
與V
是應(yīng)該盡量不要互相持有的。
這個(gè)時(shí)候如何把原本松散的二者通過(guò)C
緊密的聯(lián)系起來(lái)供鸠、就要進(jìn)行數(shù)據(jù)綁定畦贸。
而這種數(shù)據(jù)綁定、iOS本身并沒(méi)有什么太靠譜的辦法(就像剛才前端例子中的<p>{{message}}</p>
楞捂、這種寫(xiě)法)薄坏。
雖然KVO、Notification寨闹、block胶坠、delegate和target-action都可以用來(lái)做數(shù)據(jù)通信進(jìn)而實(shí)現(xiàn)綁定
但都不如ReactiveCocoa來(lái)的《《《優(yōu)雅》》》。
對(duì)繁堡、這就是我開(kāi)始說(shuō)為什么RAC對(duì)于MVVM不是必須的沈善。
如果不用ReactiveCocoa乡数、綁定關(guān)系可能就做不到那么松散那么好、但并不影響它還是MVVM闻牡。
MVCS
將數(shù)據(jù)持久化的代碼移植給了store
...
MVP
實(shí)際上就是將Controller
中關(guān)于Model
與View
的調(diào)配處理的代碼移植了過(guò)來(lái)瞳脓。各部分分工如下:
- View
負(fù)責(zé)界面展示和布局管理、向Presenter暴露視圖更新和數(shù)據(jù)獲取的接口 - Presenter
負(fù)責(zé)接收來(lái)自View的事件澈侠、通過(guò)View提供的接口更新視圖劫侧,并管理Model。 - Model
和MVC中的一樣哨啃,提供數(shù)據(jù)模型
VIPER
除了View沒(méi)拆烧栋、其它的都拆了....
在MVP的基礎(chǔ)上新增了Interactor與Router
- View
- 提供完整的視圖。負(fù)責(zé)視圖的組合拳球、布局审姓、更新
- 向Presenter提供更新視圖的接口
- 將View相關(guān)的事件發(fā)送給Presenter
- Interactor
- 維護(hù)主要的業(yè)務(wù)邏輯功能,向Presenter提供現(xiàn)有的業(yè)務(wù)用例
- 維護(hù)祝峻、獲取魔吐、更新Entity
- 當(dāng)有業(yè)務(wù)相關(guān)的事件發(fā)生時(shí)、處理事件莱找、并通知Presenter
- Presenter
- 接收并處理來(lái)自View的事件
- 向Interactor請(qǐng)求調(diào)用業(yè)務(wù)邏輯
- 向Interactor提供View中的數(shù)據(jù)
- 接收并處理來(lái)自Interactor的數(shù)據(jù)回調(diào)事件
- 通知View進(jìn)行更新操作
- 通過(guò)Router跳轉(zhuǎn)到其他View
- Entity
- 和Model一樣的數(shù)據(jù)模型
- Router
- 提供View之間的跳轉(zhuǎn)功能酬姆、減少了模塊間的耦合
- 初始化VIPER的各個(gè)模塊
VIPER與其他的架構(gòu)相比最大的優(yōu)勢(shì)就是粒度簡(jiǎn)直細(xì)化成了塵埃、極大的提高了可測(cè)性奥溺。
但問(wèn)題也相當(dāng)顯著辞色、層級(jí)越多、數(shù)據(jù)傳遞的工作量(API)就越大浮定。文件越多相满、新建一個(gè)頁(yè)面的成本也就越高。
關(guān)于架構(gòu)設(shè)計(jì)桦卒、一些觀點(diǎn)
除了MVVM饶囚、它對(duì)iOS的入侵性簡(jiǎn)直太高政恍、主要取決于團(tuán)隊(duì)的決策(比如是不是新項(xiàng)目程拭、Leader是不是想玩玩看)讼渊。
-
控制好Controller的代碼量
隨著項(xiàng)目的進(jìn)行、代碼量最多只能優(yōu)化迎吵、膨脹不可避免躲撰。
而在沒(méi)辦法繼續(xù)精簡(jiǎn)的前提下针贬、想控制Controller的代碼量击费。就要在可讀性和通用性之間進(jìn)行取舍。該挪走的時(shí)候就挪走吧桦他、畢竟梳理一個(gè)單獨(dú)的模塊蔫巩、比梳理一個(gè)幾千行的Controller要方便多了谆棱。
-
對(duì)于MVX如何選擇
- 其實(shí)完全要看業(yè)務(wù)的性質(zhì)以及復(fù)雜度。
- 如果你一個(gè)頁(yè)面只有一個(gè)UITableView圆仔、搞出一些奇淫技巧其實(shí)意義 不大垃瞧、徒增煩惱。踏踏實(shí)實(shí)用MVC對(duì)大家都好坪郭。
- 如果感覺(jué)業(yè)務(wù)里有非常多的View與Model互通个从、或者需大量復(fù)用、可以用MVP歪沃。
- 如果有大量的數(shù)據(jù)讀寫(xiě)嗦锐、可以用MVCS。
- 如果業(yè)務(wù)相當(dāng)?shù)膹?fù)雜沪曙、耦合讓人渾身難受奕污。做好模塊化或者干脆VIPER才是出路。
-
無(wú)論用哪種模式液走、都要深刻的理解每個(gè)模塊不同的職責(zé)
比如MVVM里的VM碳默、既然是Model層、就不要把UIKit放進(jìn)去缘眶。
再比如MVP中的P嘱根、既然是為了幫助Controller協(xié)調(diào)M與V而生、就不要把與二者無(wú)關(guān)的工作也搶過(guò)來(lái)干巷懈。
參考資料
(要感謝評(píng)論區(qū)SlowMaker的提醒)
寫(xiě)的時(shí)候主要的結(jié)構(gòu)思路借鑒了兩年前入行時(shí)看過(guò)的一篇博客《《iOS應(yīng)用架構(gòu)談 view層的組織和調(diào)用方案》》儿子、對(duì)其中自己認(rèn)為不太好理解的部分進(jìn)行了補(bǔ)充和擴(kuò)展。
也推薦一下大神微博:@反革命攻城獅CasaTaloyum
很推薦去搜藏并且拜讀一下CasaTaloyum的博客砸喻、會(huì)讓你對(duì)架構(gòu)整體的把控有一個(gè)質(zhì)的提升柔逼。