RAC 指的就是 RactiveCocoa ,基于函数式响应式编程思想的Objective-C实践,是 Github 的一个开源框架,能够帮我们提供大量方便的事件处理方案,让我们更简单粗暴地去处理事件.使用MVVM框架就不得不提RAC.RAC具有高聚合低耦合的思想,使用RAC会让代码更简洁,逻辑更清晰。
RAC几乎接管了Apple所有的事件机制,由于RAC将Cocoa中KVO、UIKitEvent、delegate、selector等都增加了RAC支持,所以都不用去做很多跨函数的事。
ReactiveCocoa主要包含四个组件:
信号源:RACStream 及其子类;
订阅者:RACSubscriber 的实现类及其子类;
调度器:RACScheduler 及其子类;
清洁工:RACDisposable 及其子类。
而信号源是最核心的部分,其它所有组件都是围绕它运作的。
RAC 的核心思想:创建信号 - 订阅信号 - 发送信号
1. RACSignal 信号类
表示将来有数据传递,有数据改变,信号内部接收到数据,就会马上发出数据,外部就可以接收到数据了。默认信号都是冷信号,就是这个值改变了它不会触发,只有订阅(调用信号RACSignal的subscribeNext订阅)了这个信号,这个信号才会变为热信号(值一改变就触发),才会触发。
/* 创建信号 */
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
//RACSubscriber订阅者对象,用于发送信号,这是一个协议,不是一个类,只要遵守这个协议,并且实现方法才能成为订阅者。通过create创建的信号,都有一个订阅者,帮助他发送数据。
/* 发送信号 */
[subscriber sendNext:@"发送信号"];
return [RACDisposable disposableWithBlock:^{
// 当信号发送完成或者发送错误,就会自动执行这个block,取消订阅信号。
// 执行完Block后,当前信号就不在被订阅了。
NSLog(@"dealloc");
}];
//return nil;
}];
//接收信号
[signal subscribeNext:^(id _Nullable x) {
}];
也可以使用RACDisposable类直接取消订阅
/* 订阅信号 */
RACDisposable *disposable = [signal subscribeNext:^(id _Nullable x) {
//RACDisposable用于取消订阅或者清理资源,当信号发送完成或者发送错误的时候,就会自动触发它。 当你不想监听某个信号时,可以通过它主动取消订阅信号。
NSLog(@"信号内容:%@", x);
}];
/* 取消订阅 */
[disposable dispose];
2. 定时器timer(在子线程执行)
[[RACSignal interval:1.0 onScheduler:[RACScheduler scheduler]] subscribeNext:^(NSDate * _Nullable x) {
NSLog(@"当前时间:%@", x); // x 是当前的系统时间
//关闭计时器就是取消订阅
//[disposable dispose];
}];
3. RACSubject 信号提供者
RACSubject自己可以充当信号,又能发送信号。(和代理的用法类似,通常用来代替代理,有了它,就不必定义代理了)
/* 创建信号 */
RACSubject *subject = [RACSubject subject];
/* 订阅信号(通常在别的视图控制器中订阅,与代理的用法类似) */
[subject subscribeNext:^(id _Nullable x) {
/*
1创建订阅者对象
2将block放到订阅者对象中
3将订阅者对象放到subscribes数组里面
*/
NSLog(@"信号内容:%@", x);
}];
/* 发送信号 */
/*
其内部的真实操作是遍历信号对象中的数组,取出订阅者,调用订阅者中的block执行
*/
[subject sendNext:@"发送信号"];
4. RACTuple 元祖(和 OC 的数组其实是一样的,其实就是封装了我们 OC 的数组)
/* 创建元祖 */
RACTuple *tuple1 = [RACTuple tupleWithObjects:@"1", @"2", @"3", @"4", @"5", nil];
/* 从别的数组中获取内容 */
RACTuple *tuple2 = [RACTuple tupleWithObjectsFromArray:@[@"1", @"2", @"3", @"4", @"5"]];
/* 利用 RAC 宏快速封装 */
RACTuple *tuple3 = RACTuplePack(@"1", @"2", @"3", @"4", @"5");
NSLog(@"取元祖内容:%@", tuple1[0]);
NSLog(@"第一个元素:%@", [tuple2 first]);
NSLog(@"最后一个元素:%@", [tuple3 last]);
这里再说一下RACSequence这个集合类,用于代替NSArray, NSDictionary,可以使用它来快速遍历数组和字典。
//遍历数组
/*
遍历原理:
通过arr.rac_sequence把数据arr转化成集合RACSequence
通过arr.rac_sequence.signal把集合RACSequence转化成了信号
*通过subscribeNext订阅信号,遍历集合
*/
NSArray *array = @[@"1", @"2", @"3", @"4", @"5"];
[array.rac_sequence.signal subscribeNext:^(id _Nullable x) {
NSLog(@"数组内容:%@", x); // x 可以是任何对象
}];
/* 遍历字典 */
//遍历字典,遍历出来的键值对会包装成RACTuple(元组对象)
NSDictionary *dictionary = @{@"key1":@"value1", @"key2":@"value2", @"key3":@"value3"};
[dictionary.rac_sequence.signal subscribeNext:^(RACTuple * _Nullable x) {
//RACTupleUnpack是一个宏定义
RACTupleUnpack(NSString *key, NSString *value) = x; // x 是一个元祖,这个宏能够将 key 和 value 拆开
NSLog(@"字典内容:%@:%@", key, value);
}];
//下面两个方法都是将数组内容全部换为 0 ,第一个是单个操作,第二个是一次性全部替换,两个方法都不会改变原数组内容,操作完后都会生成一个新的数组,省去了创建可变数组然后遍历出来单个添加的步骤。
/* 内容操作 */
NSArray *array1 = @[@"1", @"2", @"3", @"4", @"5"];
NSArray *newArray1 = [[array1.rac_sequence map:^id _Nullable(id _Nullable value) {
NSLog(@"数组内容:%@", value);
return @"0"; // 将所有内容替换为 0
}] array];
/* 内容快速替换 */
NSArray *array2 = @[@"1", @"2", @"3", @"4", @"5"];
NSArray *newArray2 = [[array.rac_sequence mapReplace:@"0"] array]; // 将所有内容替换为 0
5. 代理
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"标题" message:@"123456" delegate:self cancelButtonTitle:@"cancel" otherButtonTitles:@"other", nil];
//可以省去监听以及设置 delegate 的步骤
[[self rac_signalForSelector:@selector(alertView:clickedButtonAtIndex:) fromProtocol:@protocol(UIAlertViewDelegate)] subscribeNext:^(RACTuple *tuple) {
NSLog(@"%@",tuple.first);
NSLog(@"%@",tuple.second);
NSLog(@"%@",tuple.third);
}];
[alertView show];
6. 通知
//RAC将收到通知后的方法封装成了block,省去了在 dealloc 中清除通知和监听通知创建方法的步骤。
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(NSNotification *notification) {
NSLog(@"%@", notification.name);
NSLog(@"%@", notification.object);
}];
7. KVO
UIScrollView *scrolView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
scrolView.contentSize = CGSizeMake(200, 800);
scrolView.backgroundColor = [UIColor redColor];
[self.view addSubview:scrolView];
//原生,其缺点是当你需要观察很多属性时,你要写很多次下面的方法,而且在观察的方法中还要去判断KeyPath具体是什么
[scrolView addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionNew context:nil];
//RAC方式1 利用 RAC 的宏,只要对象的属性发生改变就会产生信号
[RACObserve(scrolView, contentOffset) subscribeNext:^(id x) {
NSLog(@"contentOffset: %@",x);
}];
//RAC方式2
[self.redView rac_observeKeyPath:@"frame" options:NSKeyValueObservingOptionNew observer:self block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {
}];
//RAC方式3 先包装成了一个信号,只要值改变就会发送信号。
[[self.redView rac_valuesForKeyPath:@"frame" observer:self] subscribeNext:^(id _Nullable x) {
NSLog(@"frame属性的改变1:%@", x); // x 是监听属性的改变结果
}];
8. 监听 TextField 的输入改变
//可以省去设置 delegate 和实现代理方法的步骤。
[[self.textField.rac_textSignal filter:^BOOL(NSString * _Nullable value) {
return value.length > 5; // 表示输入文字长度 > 5 时才会调用下面的 block
}] subscribeNext:^(NSString * _Nullable x) {
NSLog(@"输入框内容:%@", x);
}];
[[self.textField.rac_textSignal map:^id(id value) {
return [UIColor redColor];
}] subscribeNext:^(id x) {
// NSLog(@"x:%@", x);
}];
[self.textField.rac_textSignal subscribeNext:^(NSString * _Nullable x) {
NSLog(@"输入框内容:%@", x);
}];
//给label同时赋值,其实就是用绑定信号的,只要产生信号内容,就会把内容给属性赋值
UILabel *label;
RAC(label,text) = self.textField.rac_textSignal;
9. 添加手势
//UIView
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]init];
[tap.rac_gestureSignal subscribeNext:^(id x) {
NSLog(@"x:%@", x);
self.redView.backgroundColor = [UIColor yellowColor];
}];
[self.redView addGestureRecognizer: tap];
10. 监听 Button 点击事件
//可以省去 addTarget 添加事件和创建方法的步骤。
[[self.btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
NSLog(@"点击了按钮");
}];
//登录按钮状态实时监听
//下面表示只有 用户名 和 密码 输入框内容都大于 0 时,登录 按钮才可以点击,而且状态是实时监听的,一句代码就能完成这个功能。
RAC(self.btn, enabled) = [RACSignal combineLatest:@[self.textField.rac_textSignal, self.textField.rac_textSignal] reduce:^id _Nullable(NSString * username, NSString * password){
return @(username.length && password.length);
}];
网友评论