MVVM与RAC(吹水篇)
前言
首先MVC
没什么不好,MVVM
也没多么伟大,如果你愿意,可以把MVVM
理解为特殊的MVC
,就像等边三角形、直角三角形等与普通的三角形关系一样。MVVM
是由MVC
演变而来的,我们可以在MVC
的基础上创建属于自己的开发模式。Massive View Controller
?未必!controller
作为view
和model
的管理者做好自己的事情就行(Single responsibility principle
)
- 创建
view
和model
- 管理
view
的生命周期 - 管理
view
与model
的交互逻辑 - 监听
view
事件并传给model
- 监听
model
变化并更新view
创建view
、model
很简单,在controller
初始化的时候就可以做,关于复杂界面的UI完全可以抽象出一个view
在内部完成然后再添加到controller
。监听view
、model
事件的代码怎么写?抽成一个方法/属性,在controller
中调用/赋值就可以了,这样一来controller
中也就那么几句代码。如view
、model
、网络层
、存储层
、服务层
的配合,datasource
、delegate
从controller
中分离,使用protocol
、category
、aspect
等。当然,分离、封装这种事儿过犹不及,要把握好度。最终的目的都是DRY(Don't repeat yourself )
,做好自己的事、内聚、解耦、复用。然而鱼和熊掌不可兼得,怎样取舍视具体项目而定。无论MVCS
还是MVVM
或者MVP
、VIPER
等等都是由MVC
演变过来的,只是招式,都是为了处理model-view-controller
之间的关系,不必生搬硬套,更重要的是如何处理复杂场景的业务逻辑,如何更好的DRY
。
MVVM
- MVVM or MVCVM?
先说说胖瘦model。胖model
中不单有数据也有处理数据的方法,瘦model
中只有数据。我们常用的MVC
中的model
通常都是瘦model
(实际上更像MVCS
),而MVVM
是基于胖model
构建的。MVVM
只有V
和M
,V
自然是view
、view controller
,M
就是胖model
,胖model
又可拆分成model
和view model
。与其说MVVM
,倒不如说MVCVM
更为贴切。这样一来,MVVM
不过是把MVC
中C
的交互逻辑拿出来放到VM
中去,从而达到Lighter View Controllers
的目的。按照MVCVM
来理解,MVVM
把controller
中数据处理的业务逻辑转移到view model
确实可以让controller
更专注的做自己,但是当业务逻辑很复杂同样会使view model
中凝聚大量代码,此时view model
也需要做进一步的细化。
- MVVM的核心思想
MVVM
弱化controller
,强调view model
与view
的bind
。controller
中的业务逻辑尽量转移到view model
中,除开管理view
的生命周期不谈,controller
只是起到协调view model
与view
的作用。view model
的主要职责是处理业务逻辑并给view
提供数据,view model
不关心view
从而解耦也方便做单元测试。另外,view model
中不能有view
但是可以有其他view model
。
用链状结构表示大概长这样:
View/ViewController -> ViewModel ->Model
OK,controller
持有view
和view model
,view model
处理业务逻辑并提供model
,那么如何将model
中的数据展示到view
上?这就是前面提到的将view model
与view
进行绑定,这个操作是在controller
中完成的,也就是controller
的职责之一,协调view model
与view
。
整个流程还是挺眼熟的,跟MVC
很相似。
- View Model 与 View 的绑定
view model
与view
绑定说白了就是让view model
告诉view
,现在数据变了,你该显示不同的内容。但是view model
不可以持有view
,因此赋值操作无法写在view model
里而是在controller
中完成。其实可选的方法很多,block
、delegate
、notification
、KVO
、target-action/invocation
...
如果非要说优雅这个词,不谈RAC
似乎显得太low,但是就像前面所说,可供选择的方法很多,不是非RAC
不可。
RAC
说起RAC
不得不说Stream
这个概念,stream
翻译过来是流
的意思,用流
来组词我们最先想到可能是水流
、电流
,会有动态的画面感,流动
即驱动,驱动的原动力是事件流
。RAC
就是基于stream
这个概念发展来的,即所谓的函数响应式编程( Functional Reactive Programming:FRP )
。
- RACStream
RACStream
有两个子类:RACSignal
和RACSequence
。Signal
表示信号
,Sequence
表示序列
,两者都可以很好的描述流
所拥有的特性。
信号
可发送、可接收、可合并、可压缩、可增删改查、可...。这些特性流同样拥有,信号即流,万物皆流,拥有流即可改造万物。
诗云:江河入海流
。序列
可理解为大海,海纳百川
。序列
可以汇集流
,形成一个更庞大的流
,也可以分离出所汇集的每一个流
。
- RACSignal
signal
存在的意义就是加工并传递数据,所以signal
需要一个订阅者subscriber
来接收数据。有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)];
}
可见,这个方法内部已经配置了订阅者,所以可以直接工作。订阅者内部拷贝next
、error
、completed
这三个block,其中error
、completed
为NULL
。next
是基于textField的UIControlEventAllEditingEvents
事件触发的,block内参数为textField.text。至此,从心理上起码可以接受RAC
这种东西。
凡事要学会抓主要矛盾,切勿眉毛胡子一把抓
。先别管代码里种种看不明白的天书,来看看这个方法做了什么,为啥可以监听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
的实例,action
则为RACSubscriber
的协议方法- (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后执行nextBlock
这个block。那么self.next又是啥?就是例子开始中的block
[_textField.rac_textSignal subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
再来理一遍思路看看RAC
是如何一句代码完成对textField
的UIControlEventAllEditingEvents
事件监听的。
1 将textField信号化并通过block将
target-action
转移到订阅者
2 创建订阅者并在订阅者内部拷贝外部subscribeNext
中的block
3 当触发UIControlEventAllEditingEvents
事件通过订阅者回调外部block
如果你已经理解这些,我们继续吧。回到这个方法,来看看天书们是否安好
@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)
// 创建一个信号,当有订阅者订阅信号,将冷信号转换为热信号
+ (RACSignal *)defer:(RACSignal * (^)(void))block;
@interface RACSignal (RACStream)
// 将信号传递给value
+ (RACSignal *)return:(id)value;
// 衔接新旧信号,旧信号执行完执行新信号
- (RACSignal *)concat:(RACSignal *)signal;
@interface RACStream (Operations)
// 信号映射
- (instancetype)map:(id (^)(id value))block;
// 当`signalTrigger`执行`next`或`completed`时,返回信号执行`completed`
- (RACSignal *)takeUntil:(RACSignal *)signalTrigger;
什么乱七八糟的。。!先别想这么多,试着结合上文把刚刚看到的注释拼接起来带入- (RACSignal *)rac_textSignal
中可以得到这么一句话:
创建一个可变为热信号的冷信号,并将这个信号传递给textField,当信号传递完毕,利用target-action
让订阅者监听UIControlEventAllEditingEvents
事件,映射当前信号并作将textField.text做为block参数返回,这一系列操作直到textField.rac_willDeallocSignal
执行next
或者completed
时结束。
那么这句代码的意思已经很清楚了
[_textField.rac_textSignal subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
_textField.rac_textSignal
完成事件监听并管理信号的生命周期,subscribeNext
创建订阅者订阅_textField.rac_textSignal
监听事件。
似乎有点偏离主题,关于更多RAC
知识,下篇文章接着说。