MVVM和ReactiveCocoa
在MVVM中UIViewController讲专注的负责View的管理、界面流程跳转,它不会直接持有、处理数据。UIViewController需要准备数据来组织、展现View,这时它需要通过对应的View-Model来准备,数据的获取、处理、持有都由View-Model来负责。估计这时大多数人心里有有了一些想法:View-Model在处理准备好数据以后,完全可以通过代理、block来实现通知UIViewController刷新界面。这个想法也是可以的,不使用ReactiveCocoa也可以实现MVVM开发模式,但是我还是愿意花时间来学习、了解ReactiveCocoa。初始接触ReactiveCocoa时,感觉它是一个很怪异的东东,以前“正”着写的,现在需要“反”着写,语法怪异,理解困难。其他朋友们有必要花时间学习吗?
目前有一个很火的理念:数据与视图绑定。就是当数据变化时,视图不需要额外的处理,便可正确地呈现最新的数据。而这也是ReactiveCocoa最重要的亮点。RAC与MVVM编程模式结合,可以方便的处理界面变化,减少View-Model与UIViewController连接,让我们两层的核心功能。
ReactiveCocoa不是单一功能的模块,它是一个Framework,提供了一整套解决方案。其核心思想是「响应数据的变化」,在这个基础上有了Signal的概念,进而可以帮助减少状态类型,易于使用MVVM架构,方便的响应式编程等等。
为什么要使用ReactiveCocoa
- 开发过程中,状态以及状态之间依赖过多,RAC更加有效率地处理事件流,而无需显式去管理状态。在过程式编程中,状态变化是最难跟踪,最头痛的事。这个是我使用RAC最重要的一点。
- 减少方法的调用,由于它跟踪状态和值的变化,因此不需要状态更新时手动调用,减少出错的可能。
- 提供统一的消息传递机制,将OC中的通知、代理,KVO以及其它所有UIControl事件的变化都进行监控,当变化发生时,就会传递事件和值。
- 当值随着事件变换时,可以使用combineLatest、map、filter等函数便利地对值进行变换操作。
比如:我们想要实现一个需求:当“count”变量中的字符串改变后即时做出相应的反馈。用KVO我们一般会这样做
<pre>`
// In your class viewDidLoad/init
[self addObserver:self
forKeyPath:@"count"
options:NSKeyValueObservingOptionNew
context:nil];
// In dealloc
[self removeObserver:self
forKeyPath:@"count"
context:nil];
// rewrite in your class
- (void)observeValueForKeyPath:(NSString)keyPath
ofObject:(id)object
change:(NSDictionary)change
context:(void *)context
{
if ([keyPath isEqualToString:@"count"]) {
//do ....
}
}
`</pre>
如果我们在工程中使用ReactiveCocoa后,只有短短几行,还无需重写方法
<pre>[RACObserve(self, count) subscribeNext:^(NSNumber *count) { //do .... }];
</pre>
看到RAC的实现是不是觉得很简单,代码简洁清晰,下面了解一下RAC
RACSignal
RAC为应用中发生的不同事件流提供了一个标准接口。在ReactiveCocoa术语中这个叫做信号,由RACSignal类表示。RAC的核心就是RACSignal发送事件流给它的订阅者,共有三种类型的事件:next、error、completed。一个signal可以在completed前发送任意数量的next事件;也会因为error终止。简单的说就是error、completed只能发送一次,在completed前可以发送多次next事件。
信号是一个发送一连串信息的载体体. 如果它还没有订阅者,那么它就是一个冷信号,现在不会起任何作用。只有信号被订阅了,它才会向它的订阅者发送信息,这时信号处于激活状态,称之为热信号。
冷信号默认什么也不干,所以我们需要避免在开发过程中创建一堆冷信号,比如下面这段代码,如果不被订阅,或者不被连接,那么它没有任何意义。
<pre>RACSignal *signal = [RACSignal createSignal:^ RACDisposable * (id subscriber) { NSLog(@"signal"); [subscriber sendNext:@"1"]; [subscriber sendNext:@"2"]; [subscriber sendCompleted]; return nil; }];
</pre>
我们已经创建了一个冷信号signal,但因为没有被subscribe或连接,所以什么也不会发生。
加了下面这段代码后,signal就处于激活状态了,block里的代码就会被执行。
订阅示例:
<pre>[signal subscribeNext:^(NSString *name):^{ NSLog(@"name = %@", name); }];
</pre>
连接示例:
<pre>RAC(self.usernameField, text) = RACObserve(self, name);
</pre>
RACSignal有很多方法可以来订阅不同的事件类型。每个方法都需要至少一个block,当事件发生时就会执行block中的逻辑。比如每次next事件发生时,subscribeNext:方法提供的block都会执行。
<blockquote>
(RACDisposable *)subscribeNext:(void (^)(id x))nextBlock;
(RACDisposable *)subscribeNext:(void (^)(id x))nextBlock completed:(void (^)(void))completedBlock;
(RACDisposable *)subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock;
(RACDisposable *)subscribeError:(void (^)(NSError *error))errorBlock;
(RACDisposable *)subscribeCompleted:(void (^)(void))completedBlock;
(RACDisposable *)subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock;
(RACDisposable *)subscribeError:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock;
</blockquote>
ReactiveCocoa框架使用category来为很多基本UIKit控件添加signal。这样你就能给控件添加订阅了,如UITextField添加了rac_textSignal。
创建RACSignal
使用
+(RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe
方法创建信号,在block需要实现“RACSubscriber”协议,最后还需返回RACDisposable,用于处理、销毁信号。
<pre>`
RACSignal *signal = [RACSignal createSignal:^ RACDisposable * (id subscriber) {
NSLog(@"signal");
[subscriber sendNext:@"1"];
[subscriber sendNext:@"2"];
[subscriber sendCompleted];
return nil;
}];
[signal subscribeNext:^(NSString *name):^{
NSLog(@"name = %@", name);
}];
`</pre>
使用“RACObserve”宏定义快捷创建信号
<pre>[RACObserve(self, name) subscribeNext:^(NSString *name) { NSLog(@"count %@", name); }];
</pre>
这些是一些简单的用法,下面给一个实用的,在开发过程中可以实用的示例,这是一个根据URL获取数据的RACSignal创建示例:
<pre>`
pragma mark - Get请求
-
(RACSignal *)fetchGetServerFromURL:(NSURL *)url
{GLogString(url.description);
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];//申明请求的数据是json类型 manager.requestSerializer = [AFJSONRequestSerializer serializer]; manager.requestSerializer.timeoutInterval = 30.0f; //申明返回的结果是json类型 manager.responseSerializer = [AFJSONResponseSerializer serializer]; //如果报接受类型不一致请替换一致text/html或别的 manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:@"text/html"]; AFHTTPRequestOperation *operation = [manager GET:url.absoluteString parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) { GLogString(responseObject); MBAResponse *response = [MBAResponse responseWithJsonDict:responseObject]; if (response.requestSuccess) { [subscriber sendNext:response.dataDict]; [subscriber sendCompleted]; } else { GLogString(response.error.localizedDescription); [subscriber sendError:response.error]; } } failure:^(AFHTTPRequestOperation *operation, NSError *error) { [subscriber sendError:error]; }]; return [RACDisposable disposableWithBlock:^{ //信号销毁时,取消请求 [operation cancel]; }];
}];
}
`</pre>
信号创建以后,订阅者可以获取数据
<pre>`
//创建获取数据的信号
RACSignal *signal = [self fetchGetServerFromURL:[NSURL URLWithString:@"http://www.sina.com"]];
//订阅信号,编写信号数据流处理
RACDisposable *disposable = [signal subscribeNext:^(id x) {
NSLog(@"data= %@", x);
} error:^(NSError *error) {
NSLog(@"error = %@", error);
}];
`</pre>
在请求的过程中,我们如果不需要了,也可以取消请求
<pre>`
//创建获取数据的信号
RACSignal *signal = [self fetchGetServerFromURL:[NSURL URLWithString:@"http://www.sina.com"]];
//订阅信号,编写信号数据流处理
RACDisposable *disposable = [signal subscribeNext:^(id x) {
NSLog(@"data= %@", x);
} error:^(NSError *error) {
NSLog(@"error = %@", error);
}];
//取消请求
if (!disposable.isDisposed) {
[disposable dispose];
}
`</pre>
当我们手动销毁信号,这时之前创建信号时生成的RACDisposable对象就会生效
<pre>return [RACDisposable disposableWithBlock:^{ //信号销毁时,取消请求 [operation cancel]; }];
</pre>
这一连串代码示例就是信号创建-订阅-处理(或取消)的过程,只要先理解清楚信号的工作原理,在后面的学习、应用中,会发现RAC更加便捷,更加简单。
总结
这张主要说了RAC的优点以及基本用法,这些基本用法能应对开发过程中各种复杂的需求吗,下面会说说RAC的一下高级用法。