MVVM+RAC使用体验(附demo)
MVVM介绍
MVVM是在MVC的基础上演变而来的,MVC的核心就是将各个层级进行划分,尽量把界面、业务控制、模型划分开来,但是iOS应用开发是建立在一个个ViewController基础上,所以你觉得ViewController在MVC的架构中担任什么角色呢?view还是controller?在过去很长的一段时间中,它既扮演view的角色又同时要承担controller的责任,这样的viewcontroller让开发者很头痛,也让维护者心累,在MVC模式下,随着功能的丰富,ViewController已经很难开发和维护了,所以MVVM和MVP这些新的架构模式被提出了,这里我们重点将MVVM模式,在之后会补充MVP开发模式。MVVM模式可以看成是MVC的一次瘦身体验,我现在的项目就是从MVC转成MVVM的,转完之后我感觉自身都瘦了(哈哈)。MVVM=Model+View+ViewModel,其中model和MVC中的M基本一样,View则完成之前ViewController的UI功能,ViewModel负责之前ViewController的业务逻辑。MVVM理解起来很简单,在实际操作中只要控制好view和viewmodel的相互绑定就能很好的进行分层解耦,说到解耦必然会涉及到如何进行消息的传递了,iOS常用的方式有block、delegate、NSNotify、target-action、KVO、ReactiveCocoa方式,这里我们使用ReactiveCocoa方式进行实现。
ReactiveCocoa介绍
ReactiveCocoa(RAC)是GitHub团队设计出的一种消息传递机制。相较于单纯的block和delegate,RAC的好处有:
- 链式编程 提高代码的可读性
- 数据驱动 随着信号的传递和改变来改变事件
- 统一的消息机制方便管理
- 在基础消息传递基础上对外公开了一些使用的api,如skip、concat等
更多的RAC好处还要coder在实际使用中自己体会,不过因为RAC对事件传递封装的层次较多,所以要掌握一些调试技巧,我一般是通过观察线程栈来判断错误点,另外我建议,项目中非导入类都统一添加项目的前缀。
说完RAC的好处和缺点,在简单讲述下其实现原理,RAC把业务中的每个数据作为信号,而不同的操作则可以把这个信号传递到不同方向,信号的终点则是我们的业务期望,所以你可以用同一个信号,在不同条件下可以得到不同结果,这个条件可以具像化成天气的不同等。其实ReactiveCocoa不一定要搭配MVVM一起使用,但是RAC却天然成为了MVVM的首选,因为MVVM模式的关键点就在于View和ViewModel的绑定,而RAC很容易进行这种绑定。下面会根据demo中的代码进行分析。
RAC常用类介绍
RACSignal的使用
信号的使用通常分为三个步骤:1、创建信号;2、订阅信号;3、发送信号;
其中信号分为冷信号和热信号,默认通过[RACSignal create]
创建的RACSignal的信号默认时冷信号,在没被订阅前即使值改变了也是不会被执行的。
在创建信号的时,可以实现RACDisposable的block,在RACDisposable的block中可以完成资源的释放,默认在信号发送完数据后都会主动调用block代码,但是如果订阅者被强引用且没有发送完成信号或者错误信号的话block是不会被调用的,这个时候可以手动取消订阅,可以对比一下代码:
//信号1
self.coolSignal1 = [RACSignal createSignal:^RACDisposable *(id subscriber) {
[subscriber sendNext:@"冷信号1发送内容"];
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^{
NSLog(@"结束1");
}];
}];
//信号2
self.coolSignal2 = [RACSignal createSignal:^RACDisposable *(id subscriber) {
self.subscriber = subscriber;
[subscriber sendNext:@"冷信号2发送内容"];
return [RACDisposable disposableWithBlock:^{
NSLog(@"结束2");
}];
}];
RACSubject的使用
该对象即可以作为信号的订阅者也可以作为信号的发送者,可以很好的代替delegate,但是必须要先进行订阅才能接受值的变化,在项目开发过程中,信号的发送和订阅先后顺序不会在同一代码块中,所以请开发者注意先后顺序。可以对比一下代码,第一个信号不能被成功获得值,第二个信号可以:
//只有先订阅再发送信号,订阅者才能接受值
- (IBAction)subjectTestAction:(id)sender {
RACSubject *tempSubject = [RACSubject subject];
[tempSubject sendNext:@"123"];
[tempSubject subscribeNext:^(id x) {
NSLog(@"subscriber %@",x);
}];
}
- (IBAction)subjectTestAction2:(id)sender {
RACSubject *tempSubject = [RACSubject subject];
[tempSubject subscribeNext:^(id x) {
NSLog(@"subscriber %@",x);
}];
[tempSubject sendNext:@"1234"];
}
RACReplaySubject的使用
如果想先发送信号再进行订阅可以使用RACSubject的子类RACReplaySubject,该子类可以先保存发送的信号,然后遍历订阅者,将之前发送的所有值都传人订阅者,如下:
- (IBAction)replaySubjectTestAction:(id)sender {
RACReplaySubject *tempReplaySubject = [RACReplaySubject subject];
//可以先发信号再订阅
[tempReplaySubject sendNext:@"123"];
[tempReplaySubject sendNext:@"1234"];
[tempReplaySubject subscribeNext:^(id x) {
NSLog(@"subscriber %@",x);
}];
}
RACMulticastConnection的使用
在项目开发过程中,同一个信号获取因为需求被多个订阅者订阅,如果直接使用冷信号的话,信号的block会被多次调用,例如block中是网络请求或者其他占用资源的操作,这种操作是很不友好的,所以就引入了RACMulticastConnection类,其实RACMulticastConnection是经常被用来生成热信号的方法,更多关于热信号的使用可以在之后的文章中了解,该类可以只调用一次原始信号block,如下代码中repeatSubscriberTestAction
会进入三次block而connectTestAction
只会进入一次:
- (IBAction)repeatSubscriberTestAction:(id)sender {
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
NSLog(@"进入block");
[subscriber sendNext:nil];
return nil;
}];
[signal subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
[signal subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
[signal subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
}
- (IBAction)connectTestAction:(id)sender {
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
NSLog(@"进入block");
[subscriber sendNext:nil];
return nil;
}];
RACMulticastConnection *connection = [signal publish];
[connection.signal subscribeNext:^(id x) {
NSLog(@"subscriber1");
}];
[connection.signal subscribeNext:^(id x) {
NSLog(@"subscriber2");
}];
[connection.signal subscribeNext:^(id x) {
NSLog(@"subscriber3");
}];
[connection connect];
}
RACCommand的使用
RACCommand常用于网络请求和按钮等UI控件事件,RACComand有个属性allowsConcurrentExecution默认是NO,从而可以防止当命令处于执行状态时不会接受新的订阅。RACCommand执行的时候返回的是一个信号RACSignal,所以可以执行内容进行信号的传递。RACCommand的executionSignals是信号的信号,所以在捕获发送内容时可以通过使用switchToLatest或flatten的方法来做到或者直接进行订阅。使用步骤如下:
//创建command,一般这种实现业务逻辑的操作放在viewmodel中
- (RACCommand *)command {
if (!_command) {
_command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
return [RACSignal createSignal:^RACDisposable *(id subscriber) {
NSLog(@" 此处可以进行网络请求或者其他的操作,操作结果可以通过订阅者send出去");
//模拟一个网络请求
dispatch_async(dispatch_queue_create(0, 0), ^{
[subscriber sendNext:@"事件处理结果"];
[subscriber sendCompleted];
});
return nil;
}];
}];
}
return _command;
}
//command处理结果,如果涉及UI的变化可以在viewcontroller或者view中进行订阅
[[[self.viewModel.command.executionSignals switchToLatest] deliverOnMainThread] subscribeNext:^(id x) {
NSLog(@"捕获结果再处理ui层的变化");
}];
本篇章只是简单介绍了MVVM和RAC常用类,不过在小型项目中这些类应该就能满足使用了,更多关于RAC的一些好用的api和降低耦合度的方法(路由)会在下篇文章中更新
文章对应的demo