前言
首先MVC沒什么不好,MVVM也沒多么偉大,如果你愿意,可以把MVVM理解為特殊的MVC,就像等邊三角形、直角三角形等與普通的三角形關(guān)系一樣。MVVM是由MVC演變而來的,我們可以在MVC的基礎(chǔ)上創(chuàng)建屬于自己的開發(fā)模式。Massive View Controller?未必!controller作為view和model的管理者做好自己的事情就行(Single responsibility principle)
- 創(chuàng)建
view和model - 管理
view的生命周期 - 管理
view與model的交互邏輯 - 監(jiān)聽
view事件并傳給model - 監(jiān)聽
model變化并更新view
創(chuàng)建view、model很簡單,在controller初始化的時候就可以做,關(guān)于復(fù)雜界面的UI完全可以抽象出一個view在內(nèi)部完成然后再添加到controller。監(jiān)聽view、model事件的代碼怎么寫?抽成一個方法/屬性,在controller中調(diào)用/賦值就可以了,這樣一來controller中也就那么幾句代碼。如view、model、網(wǎng)絡(luò)層、存儲層、服務(wù)層的配合,datasource、delegate從controller中分離,使用protocol、category、aspect等。當(dāng)然,分離、封裝這種事兒過猶不及,要把握好度。最終的目的都是DRY(Don't repeat yourself ),做好自己的事、內(nèi)聚、解耦、復(fù)用。然而魚和熊掌不可兼得,怎樣取舍視具體項(xiàng)目而定。無論MVCS還是MVVM或者MVP、VIPER等等都是由MVC演變過來的,只是招式,都是為了處理model-view-controller之間的關(guān)系,不必生搬硬套,更重要的是如何處理復(fù)雜場景的業(yè)務(wù)邏輯,如何更好的DRY。
MVVM
- MVVM or MVCVM?
先說說胖瘦model。胖model中不單有數(shù)據(jù)也有處理數(shù)據(jù)的方法,瘦model中只有數(shù)據(jù)。我們常用的MVC中的model通常都是瘦model(實(shí)際上更像MVCS),而MVVM是基于胖model構(gòu)建的。MVVM只有V和M,V自然是view、view controller,M就是胖model,胖model又可拆分成model和view model。與其說MVVM,倒不如說MVCVM更為貼切。這樣一來,MVVM不過是把MVC中C的交互邏輯拿出來放到VM中去,從而達(dá)到Lighter View Controllers的目的。按照MVCVM來理解,MVVM把controller中數(shù)據(jù)處理的業(yè)務(wù)邏輯轉(zhuǎn)移到view model確實(shí)可以讓controller更專注的做自己,但是當(dāng)業(yè)務(wù)邏輯很復(fù)雜同樣會使view model中凝聚大量代碼,此時view model也需要做進(jìn)一步的細(xì)化。
- MVVM的核心思想
MVVM弱化controller,強(qiáng)調(diào)view model與view的bind。controller中的業(yè)務(wù)邏輯盡量轉(zhuǎn)移到view model中,除開管理view的生命周期不談,controller只是起到協(xié)調(diào)view model與view的作用。view model的主要職責(zé)是處理業(yè)務(wù)邏輯并給view提供數(shù)據(jù),view model不關(guān)心view從而解耦也方便做單元測試。另外,view model中不能有view但是可以有其他view model。
用鏈狀結(jié)構(gòu)表示大概長這樣:
View/ViewController -> ViewModel ->Model
OK,controller持有view和view model,view model處理業(yè)務(wù)邏輯并提供model,那么如何將model中的數(shù)據(jù)展示到view上?這就是前面提到的將view model與view進(jìn)行綁定,這個操作是在controller中完成的,也就是controller的職責(zé)之一,協(xié)調(diào)view model與view。
整個流程還是挺眼熟的,跟MVC很相似。
- View Model 與 View 的綁定
view model與view綁定說白了就是讓view model告訴view,現(xiàn)在數(shù)據(jù)變了,你該顯示不同的內(nèi)容。但是view model不可以持有view,因此賦值操作無法寫在view model里而是在controller中完成。其實(shí)可選的方法很多,block、delegate、notification、KVO、target-action/invocation...
如果非要說優(yōu)雅這個詞,不談RAC似乎顯得太low,但是就像前面所說,可供選擇的方法很多,不是非RAC不可。
RAC
說起RAC不得不說Stream這個概念,stream翻譯過來是流的意思,用流來組詞我們最先想到可能是水流、電流,會有動態(tài)的畫面感,流動即驅(qū)動,驅(qū)動的原動力是事件流。RAC就是基于stream這個概念發(fā)展來的,即所謂的函數(shù)響應(yīng)式編程( Functional Reactive Programming:FRP )。
- RACStream
RACStream有兩個子類:RACSignal和RACSequence。Signal表示信號,Sequence表示序列,兩者都可以很好的描述流所擁有的特性。
信號可發(fā)送、可接收、可合并、可壓縮、可增刪改查、可...。這些特性流同樣擁有,信號即流,萬物皆流,擁有流即可改造萬物。
詩云:江河入海流。序列可理解為大海,海納百川。序列可以匯集流,形成一個更龐大的流,也可以分離出所匯集的每一個流。
- RACSignal
signal存在的意義就是加工并傳遞數(shù)據(jù),所以signal需要一個訂閱者subscriber來接收數(shù)據(jù)。有subscriber的signal稱為熱信號,沒有subscriber的signal稱為冷信號,沒有意義。
舉個栗子:

很方便吧,然而并沒有什么...用!這是怎么做到的?剛才不是還說需要subscriber嗎,為啥沒見到?貼一下源碼:
@implementation RACSignal
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
NSCParameterAssert(nextBlock != NULL);
RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
return [self subscribe:o];
}
@implementation RACSubscriber
+ (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed {
RACSubscriber *subscriber = [[self alloc] init];
subscriber->_next = [next copy];
subscriber->_error = [error copy];
subscriber->_completed = [completed copy];
return subscriber;
}
@implementation UITextField (RACSignalSupport)
- (RACSignal *)rac_textSignal {
@weakify(self);
return [[[[[RACSignal
defer:^{
@strongify(self);
return [RACSignal return:self];
}]
concat:[self rac_signalForControlEvents:UIControlEventAllEditingEvents]]
map:^(UITextField *x) {
return x.text;
}]
takeUntil:self.rac_willDeallocSignal]
setNameWithFormat:@"%@ -rac_textSignal", RACDescription(self)];
}
可見,這個方法內(nèi)部已經(jīng)配置了訂閱者,所以可以直接工作。訂閱者內(nèi)部拷貝next、error、completed這三個block,其中error、completed為NULL。next是基于textField的UIControlEventAllEditingEvents事件觸發(fā)的,block內(nèi)參數(shù)為textField.text。至此,從心理上起碼可以接受RAC這種東西。
凡事要學(xué)會抓主要矛盾,切勿眉毛胡子一把抓。先別管代碼里種種看不明白的天書,來看看這個方法做了什么,為啥可以監(jiān)聽UIControlEventAllEditingEvents事件
[self rac_signalForControlEvents:UIControlEventAllEditingEvents]
@implementation UIControl (RACSignalSupport)
- (RACSignal *)rac_signalForControlEvents:(UIControlEvents)controlEvents {
@weakify(self);
return [[RACSignal
createSignal:^(id<RACSubscriber> subscriber) {
@strongify(self);
[self addTarget:subscriber action:@selector(sendNext:) forControlEvents:controlEvents];
[self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
[subscriber sendCompleted];
}]];
return [RACDisposable disposableWithBlock:^{
@strongify(self);
[self removeTarget:subscriber action:@selector(sendNext:) forControlEvents:controlEvents];
}];
}]
setNameWithFormat:@"%@ -rac_signalForControlEvents: %lx", RACDescription(self), (unsigned long)controlEvents];
}
看到了啥?UIControl的分類以及target-action。target是RACSubscriber的實(shí)例,action則為RACSubscriber的協(xié)議方法- (void)sendNext:(id)value。Let's go on!
@implementation RACSubscriber
- (void)sendNext:(id)value {
@synchronized (self) {
void (^nextBlock)(id) = [self.next copy];
if (nextBlock == nil) return;
nextBlock(value);
}
}
拷貝self.next后執(zhí)行nextBlock這個block。那么self.next又是啥?就是例子開始中的block
[_textField.rac_textSignal subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
再來理一遍思路看看RAC是如何一句代碼完成對textField的UIControlEventAllEditingEvents事件監(jiān)聽的。
1 將textField信號化并通過block將
target-action轉(zhuǎn)移到訂閱者
2 創(chuàng)建訂閱者并在訂閱者內(nèi)部拷貝外部subscribeNext中的block
3 當(dāng)觸發(fā)UIControlEventAllEditingEvents事件通過訂閱者回調(diào)外部block
如果你已經(jīng)理解這些,我們繼續(xù)吧?;氐竭@個方法,來看看天書們是否安好
@implementation UITextField (RACSignalSupport)
- (RACSignal *)rac_textSignal {
@weakify(self);
return [[[[[RACSignal
defer:^{
@strongify(self);
return [RACSignal return:self];
}]
concat:[self rac_signalForControlEvents:UIControlEventAllEditingEvents]]
map:^(UITextField *x) {
return x.text;
}]
takeUntil:self.rac_willDeallocSignal]
setNameWithFormat:@"%@ -rac_textSignal", RACDescription(self)];
}
@interface RACSignal (Operations)
// 創(chuàng)建一個信號,當(dāng)有訂閱者訂閱信號,將冷信號轉(zhuǎn)換為熱信號
+ (RACSignal *)defer:(RACSignal * (^)(void))block;
@interface RACSignal (RACStream)
// 將信號傳遞給value
+ (RACSignal *)return:(id)value;
// 銜接新舊信號,舊信號執(zhí)行完執(zhí)行新信號
- (RACSignal *)concat:(RACSignal *)signal;
@interface RACStream (Operations)
// 信號映射
- (instancetype)map:(id (^)(id value))block;
// 當(dāng)`signalTrigger`執(zhí)行`next`或`completed`時,返回信號執(zhí)行`completed`
- (RACSignal *)takeUntil:(RACSignal *)signalTrigger;
什么亂七八糟的。。!先別想這么多,試著結(jié)合上文把剛剛看到的注釋拼接起來帶入- (RACSignal *)rac_textSignal中可以得到這么一句話:
創(chuàng)建一個可變?yōu)闊嵝盘柕睦湫盘枺⑦@個信號傳遞給textField,當(dāng)信號傳遞完畢,利用target-action讓訂閱者監(jiān)聽UIControlEventAllEditingEvents事件,映射當(dāng)前信號并作將textField.text做為block參數(shù)返回,這一系列操作直到textField.rac_willDeallocSignal執(zhí)行next或者completed時結(jié)束。
那么這句代碼的意思已經(jīng)很清楚了
[_textField.rac_textSignal subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
_textField.rac_textSignal完成事件監(jiān)聽并管理信號的生命周期,subscribeNext創(chuàng)建訂閱者訂閱_textField.rac_textSignal監(jiān)聽事件。
似乎有點(diǎn)偏離主題,關(guān)于更多RAC知識,下篇文章接著說。