本文是对ReactiveObjC部分使用介绍,原理及流程简介,见文章结尾
目录:
1、简单使用
2、UIKit (基于UIView控件)
3、Foundation (Foundation对象)
4、KVO (关于监听)
5、事件信号
6、结合网络请求使用
一、简单使用
-
RACSignal
信号相当于一个电视塔 ,只要将电视机调到跟电视塔的赫兹相同的频道,就可以收到信息。 -
subscribeNext
相当于订阅频道。当RACSignal
信号发出sendNext
消息时,subscribeNext
就可以接收到信息。
//1、创建信号
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
//任何时候,都可以发送信号,可以异步
[subscriber sendNext:@"发送信号"];
//数据传递完,最好调用sendCompleted,这时命令才执行完毕。
[subscriber sendCompleted];
return nil;
}];
//2、订阅信号
RACDisposable *disposable = [signal subscribeNext:^(id _Nullable x) {
//收到信号时
NSLog(@"信号内容:%@", x);
}];
//取消订阅
[disposable dispose];
1、调用 createSignal
创建一个信号
二、UIKit (基于UIView控件)
1、rac_textSignal
文本监听信号,可以减少对代理方法的依赖
//UITextField创建了一个 `textSignal`的信号,并订阅了该信号
//当UITextField的内容发生改变时,就会回调subscribeNext
[[self.textField rac_textSignal] subscribeNext:^(NSString * _Nullable x) {
NSLog(@"text changed = %@", x);
}];
2、filter
对订阅的信号进行筛选
//当UITextField内输入的内容长度大于5时,才会回调subscribeNext
[[[self.textField rac_textSignal] filter:^BOOL(NSString * _Nullable value) {
return value.length > 5;
}] subscribeNext:^(NSString * _Nullable x) {
NSLog(@"filter result = %@", x);
}];
3、ignore
对订阅的信号进行过滤
[[[self.textField rac_textSignal] ignore:@"666"] subscribeNext:^(NSString * _Nullable x) {
//当输入的内容 equalTo @"666" 时,这里不执行
//其他内容,均会执行subscribeNext
NSLog(@"ignore = %@", x);
}];
4、rac_signalForControlEvents
创建事件监听信号
//当UIButton点击时,会调用subscribeNext
[[self.button rac_signalForControlEvents:(UIControlEventTouchUpInside)] subscribeNext:^(__kindof UIControl * _Nullable x) {
NSLog(@"button clicked");
}];
三、Foundation (Foundation对象)
1、NSNotificationCenter
通知
//@property (nonatomic, strong) RACDisposable *keyboardDisposable;
self.keyboardDisposable = [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardDidShowNotification object:nil] subscribeNext:^(NSNotification * _Nullable x) {
NSLog(@"%@ 键盘弹起", x); // x 是通知对象
}];
注意:rac_addObserverForName
同样需要移除监听。RAC通知监听会返回一个RACDisposable清洁工的对象,在dealloc中销毁信号,信号销毁时,RAC在销毁的block中移除了监听
- (void)dealloc {
[_keyboardDisposable dispose];
}
2、 interval
定时器 (程序进入后台,再重新进入前台时,仍然有效,内部是用GCD实现的)
//创建一个定时器,间隔1s,在主线程中运行
RACSignal *timerSignal = [RACSignal interval:1.0f onScheduler:[RACScheduler mainThreadScheduler]];
//定时器总时间3秒
timerSignal = [timerSignal take:3];
//定义一个倒计时的NSInteger变量
self.counter = 3;
@weakify(self)
[timerSignal subscribeNext:^(id _Nullable x) {
@strongify(self)
self.counter--;
NSLog(@"count = %ld", (long)self.counter);
} completed:^{
//计时完成
NSLog(@"Timer completed");
}];
3、delay
延迟
//创建一个信号,2秒后订阅者收到消息
[[[RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
[subscriber sendNext:@1];
return nil;
}] delay:2] subscribeNext:^(id _Nullable x) {
NSLog(@"delay : %@", x);
}];
4、NSArray
数组遍历
NSArray *array = @[@"1", @"2", @"3", @"4", @"5"];
[array.rac_sequence.signal subscribeNext:^(id _Nullable x) {
NSLog(@"数组内容:%@", x);
}];
5、NSDictionary
字典遍历
NSDictionary *dictionary = @{@"key1":@"value1", @"key2":@"value2", @"key3":@"value3"};
[dictionary.rac_sequence.signal subscribeNext:^(RACTuple * _Nullable x) {
// x 是一个元祖,这个宏能够将 key 和 value 拆开 乱序
RACTupleUnpack(NSString *key, NSString *value) = x;
NSLog(@"字典内容:%@ : %@", key, value);
}];
6、RACSubject
代理
定义一个DelegateView视图,并且声明一个RACSubject
的信号属性,在touchesBegan
方法中,给信号发送消息
@interface DelegateView : UIView
//定义了一个RACSubject信号
@property (nonatomic, strong) RACSubject *delegateSignal;
@end
@implementation DelegateView
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 判断代理信号是否有值
if (self.delegateSignal) {
// 有值,给信号发送消息
[self.delegateSignal sendNext:@666];
}
}
@end
在UIViewController中声明DelegateView
作为属性
@interface ViewController ()
@property (nonatomic, strong) DelegateView *bView;
@end
//使用前,记得初始化
self.bView.delegateSignal = [RACSubject subject];
[self.bView.delegateSignal subscribeNext:^(id _Nullable x) {
//订阅到 666 的消息
NSLog(@"RACSubject result = %@", x);
}];
四、KVO (关于监听)
1、rac_valuesForKeyPath
通过keyPath
监听
[[self.bView rac_valuesForKeyPath:@"frame" observer:self] subscribeNext:^(id _Nullable x) {
//当self.bView的frame变化时,会收到消息
NSLog(@"kvo = %@", x);
}];
2、RACObserve
属性监听
//counter是一个NSInteger类型的属性
[[RACObserve(self, counter) filter:^BOOL(id _Nullable value) {
return [value integerValue] >= 2;
}] subscribeNext:^(id _Nullable x) {
NSLog(@"RACObserve : value = %@", x);
}];
在进行监听时,同样可以使用filter
信号,对值进行筛选
3、RAC
事件绑定
//当UITextField输入的内容为@"666"时,bView视图的背景颜色变为grayColor
RAC(self.bView, backgroundColor) = [self.textField.rac_textSignal map:^id _Nullable(NSString * _Nullable value) {
return [value isEqualToString:@"666"]?[UIColor grayColor]:[UIColor orangeColor];
}];
#define RAC(TARGET, ...)
这个宏定义是将对象的属性变化信号与其他信号关联,比如:登录时,当手机号码输入框的文本内容长度为11位时,"发送验证码" 的按钮才可以点击
五、事件信号
名词 | 描述 | 说明 |
---|---|---|
RACTuple |
元祖 | 只能存储OC对象 可以用于解包或者存储对象 |
bind |
包装 | 获取到信号返回的值,包装成新值, 再次通过信号返回给订阅者 |
concat |
合并 | 按一定顺序拼接信号,当多个信号发出的时候, 有顺序的接收信号 |
then |
下一个 | 用于连接两个信号,当第一个信号完成, 才会连接then返回的信号 |
merge |
合并 | 把多个信号合并为一个信号, 任何一个信号有新值的时候就会调用 |
zipWith |
压缩 | 把两个信号压缩成一个信号, 只有当两个信号都发出一次信号内容后, 并且把两个信号的内容合并成一个元组, 才会触发压缩流的next事件(组合的数据都是一一对应的) |
combineLatest |
结合 | 将多个信号合并起来,并且拿到各个信号的最新的值, 必须每个合并的signal至少都有过一次sendNext, 才会触发合并的信号 (combineLatest 与 zipWith不同的是,每次只拿各个信号最新的值) |
reduce |
聚合 | 用于信号发出的内容是元组, 把信号发出元组的值聚合成一个值, 一般都是先组合在聚合 |
map |
数据筛选 | map 的底层实现是通过 flattenMap 实现的 |
flattenMap |
信号筛选 | flattenMap 的底层实现是通过bind实现的 |
filter |
过滤 | 过滤信号,获取满足条件的信号 |
ps:表格排版加上
换行之后,才不至于列的内容挤到一起,累累累...
1、RACTuple
元祖
只能存储OC对象 可以用于解包或者存储对象
//解包数据
RACTupleUnpack(NSNumber *a, NSNumber *b) = x;
2、bind
包装
获取到信号返回的值,包装成新值, 再次通过信号返回给订阅者
[[self.textField.rac_textSignal bind:^RACSignalBindBlock _Nonnull{ return ^RACSignal*(id value, BOOL *stop){
// 处理完成之后,包装成信号返回出去
return [RACSignal return:[NSString stringWithFormat:@"hello: %@",value]];
};
}] subscribeNext:^(id _Nullable x) {
NSLog(@"bind : %@",x); // hello: "x"
}];
3、concat
合并
按一定顺序拼接信号,当多个信号发出的时候,有顺序的接收信号
RACSignal *signalA = [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
[subscriber sendNext:@"signalA"];
[subscriber sendCompleted];
return nil;
}];
RACSignal *signalB = [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
[subscriber sendNext:@"signalB"];
[subscriber sendCompleted];
return nil;
}];
// 把signalA拼接到signalB后,signalA发送完成,signalB才会被激活 顺序执行
[[signalA concat:signalB] subscribeNext:^(id _Nullable x) {
//先拿到 signalA 的结果 , 再拿到 signalB 的结果 , 执行两次
NSLog(@"concat result = %@", x);
}];
4、then
下一个
用于连接两个信号,当第一个信号完成,才会连接then返回的信号
// 底层实现 1.使用concat连接then返回的信号 2.先过滤掉之前的信号发出的值
[[[RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
[subscriber sendNext:@1];
[subscriber sendCompleted];
return nil;
}] then:^RACSignal *{
return [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
//可以对第一个信号的数据进行过滤处理 , 不能直接获得第一个信号的数据返回值
[subscriber sendNext:@2];
return nil;
}];
}] subscribeNext:^(id x) {
// 只能接收到第二个信号的值,也就是then返回信号的值
NSLog(@"then : %@",x); // 2
}];
5、merge
合并
把多个信号合并为一个信号,任何一个信号有新值的时候就会调用
//创建多个信号
RACSignal *mergeSignalA = [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
[subscriber sendNext:@1];
return nil;
}];
RACSignal *mergeSignalB = [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
[subscriber sendNext:@2];
return nil;
}];
// 合并信号,只要有信号发送数据,都能监听到.
RACSignal *mergeSignal = [mergeSignalA merge:mergeSignalB];
[mergeSignal subscribeNext:^(id x) {
//每次获取单个信号的值
NSLog(@"merge : %@",x);
}];
6、zipWith
压缩
把两个信号压缩成一个信号,只有当两个信号都发出一次信号内容后,并且把两个信号的内容合并成一个元组,才会触发压缩流的next事件(组合的数据都是一一对应的)
RACSignal *zipSignalA = [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
[subscriber sendNext:@1];
[subscriber sendNext:@2];
return nil;
}];
RACSignal *zipSignalB = [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
//3秒后执行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subscriber sendNext:@3];
});
//5秒后执行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subscriber sendNext:@5];
});
return nil;
}];
RACSignal *zipSignal = [zipSignalA zipWith:zipSignalB];
[zipSignal subscribeNext:^(id _Nullable x) {
// x 是一个元祖
RACTupleUnpack(NSNumber *a, NSNumber *b) = x;
NSLog(@"zip with : %@ %@", a, b);
//第一次输出 1 3
//第二次输出 2 5
}];
7、combineLatest
结合
将多个信号合并起来,并且拿到各个信号的最新的值,必须每个合并的signal至少都有过一次sendNext,才会触发合并的信号 (combineLatest 与 zipWith不同的是,每次只拿各个信号最新的值)
RACSignal *combineSignalA = [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
[subscriber sendNext:@1];
[subscriber sendNext:@2];
return nil;
}];
RACSignal *combineSignalB = [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subscriber sendNext:@3];
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subscriber sendNext:@5];
});
return nil;
}];
RACSignal *combineSignal = [combineSignalA combineLatestWith:combineSignalB];
[combineSignal subscribeNext:^(id _Nullable x) {
// x 是一个元祖
RACTupleUnpack(NSNumber *a, NSNumber *b) = x;
NSLog(@"combineLatest : %@ %@", a, b);
//第一次输出 2 3
//第二次输出 2 5
//因为combineSignalA中的2是最新数据,所以,combineSignalA每次获取到的都是2
}];
8、reduce
聚合
用于信号发出的内容是元组,把信号发出元组的值聚合成一个值,一般都是先组合在聚合
RACSignal *reduceSignalA = [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
[subscriber sendNext:@1];
return nil;
}];
RACSignal *reduceSignalB = [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
[subscriber sendNext:@3];
return nil;
}];
RACSignal *reduceSignal = [RACSignal combineLatest:@[reduceSignalA, reduceSignalB] reduce:^id(NSNumber *a, NSNumber *b) {
//reduce中主要是对返回数据的处理
return [NSString stringWithFormat:@"%@ - %@", a, b];
}];
[reduceSignal subscribeNext:^(id _Nullable x) {
//返回值x 取决于reduce之后的返回
NSLog(@"reduce : %@", x);
}];
9、map
数据过滤
map
的底层实现是通过flattenMap
实现的。map
直接对数据进行处理,并且返回处理后的数据
[[self.textField.rac_textSignal map:^id _Nullable(NSString * _Nullable value) {
// 当源信号发出,就会调用这个block,修改源信号的内容
// 返回值:就是处理完源信号的内容。
return [NSString stringWithFormat:@"hello : %@",value];
}] subscribeNext:^(id _Nullable x) {
NSLog(@"Map : %@",x); // hello: "x"
}];
10、flattenMap
信号过滤
flattenMap
的底层实现是通过bind
实现的。拿到原数据,处理完成之后,包装成信号返回
[[self.textField.rac_textSignal flattenMap:^__kindof RACSignal * _Nullable(NSString * _Nullable value) {
return [RACSignal return:[NSString stringWithFormat:@"hello : %@", value]];
}] subscribeNext:^(id _Nullable x) {
NSLog(@"flattenMap : %@", x); // hello "x"
}];
10、filter
过滤
过滤信号,获取满足条件的信号
[[self.textField.rac_textSignal filter:^BOOL(NSString *value) {
return value.length > 6;
}] subscribeNext:^(NSString * _Nullable x) {
NSLog(@"filter : %@", x); // x 值位数大于6
}];
六、结合网络请求使用
以下网络接口均基于MVVM模式
1、请求单个接口
//创建请求接口的信号,该方法可以定义并实现在ViewModel层
#pragma mark - 获取指定时间的课程
+ (RACSignal *)getCourseInfoByTime:(NSInteger)time {
//此处为接口所需参数
NSMutableDictionary *paramter = [NSMutableDictionary dictionary];
[paramter setObject:@(time) forKey:@"exerciseTime"];
return [RACSignal createSignal:^RACDisposable *(id subscriber) {
//这里为接口请求方法
//接口url: CombinePath(TY_DEBUG_HOST, SPORT_HOME_COURSE)
//postRequest:接口请求类型 post
//paramter:参数
//[SportCourseDataModel class]:接口返回类型model
[Networking requestWithPath:CombinePath(TY_DEBUG_HOST, SPORT_HOME_COURSE) requestType:postRequest requestParamter:paramter responseObjctClass:[SportCourseDataModel class] completionBlock:^(BOOL isSuccess, id object, NSError *error) {
//当接口返回结果后,根据状态,分别传递object或者error给订阅者
if (isSuccess) {
//将接口返回的接口object传递给subscribeNext
[subscriber sendNext:object];
//信号完成之后,最好调用sendCompleted
[subscriber sendCompleted];
}
else {
[subscriber sendError:error];
}
}];
return nil;
}];
}
//信号订阅
//在ViewController中定义一个方法,用来调用网络接口方法
- (void)getCourseByIsExperience:(BOOL)isExperience {
//使用 @weakify(self) 和 @strongify(self) 避免循环引用
@weakify(self)
[[SportViewModel getCourseInfoByTime:0 isExperience:isExperience] subscribeNext:^(id _Nullable x) {
@strongify(self)
//接口请求成功,订阅者可以在这里获取到接口返回的内容 x
} error:^(NSError * _Nullable error) {
@strongify(self)
//当接口出错时,这里可以处理错误信息
}];
}
分析:
1、在ViewModel类中,创建了一个信号,这个信号请求了一个获取课程的接口。信号创建之后,并不会立即执行,要等订阅者,订阅并调用subscribeNext
时,才会执行。
2、在ViewController中,经过用户操作,开始调用getCourseByIsExperience
方法。此时,订阅者开始订阅信号,信号中的createSignal
开始执行接口请求方法。
3、当接口请求成功后,根据状态,将对应的object或者error通过sendNext:
和sendError:
传递给订阅者
4、订阅者开始执行subscribeNext
或者 error
block中的代码
(ps:如果接口请求之后,不需要获取返回值,则可以在信号中这样返回 [subscriber sendNext:nil]
)
优点:这个接口请求过程,ViewController只需要将接口所需参数传入,即可得到接口的结果,大大简化了控制器层面的内容,使得控制器更加专注于页面之间的业务处理,数据传递等功能。
2、多个接口的同时调用 (以下的接口信号创建过程,不再描述)
//获取血压收缩的数据 接口信号
RACSignal *systolicSignal = [DataStatisticsViewModel getItemDataByPersonId:personId baseItemId:self.systolicItemModel.baseItemId];
//获取血压舒张压的数据 接口信号
RACSignal *diastolicSignal = [DataStatisticsViewModel getItemDataByPersonId:personId baseItemId:self.diastolicItemModel.baseItemId];
@weakify(self)
//因为两个接口是需要同时获取到数据的,所以可以使用combineLatest组合信号
[[RACSignal combineLatest:@[systolicSignal, diastolicSignal]] subscribeNext:^(RACTuple * _Nullable x) {
@strongify(self)
//因为是请求了多个接口,所以会有多个数据返回,此处的x是一个元祖,所以使用RACTupleUnpack解包元祖
//返回结果值(DataItemRecordModel)的顺序对应combineLatest中数组的信号顺序
RACTupleUnpack(DataItemRecordModel *systolicModel, DataItemRecordModel *diastolicModel) = x;
//这里可以直接使用返回值 systolicModel 和 diastolicModel
} error:^(NSError * _Nullable error) {
@strongify(self)
//没有数据
[self handleTheErrorMessage:error];
}];
多个接口同时调用的过程同单个接口请求类似。
需注意:
(一)多个接口同时请求时,只要有其中一个返回错误信息,整个结果即为失败,即会走error:^(NSError * _Nullable error){}
这个block,所以必须多个接口都成功时,才会调用subscribeNext:^(RACTuple * _Nullable x){}
block。
(二)可以对结果先做聚合处理,返回再返回结果,比如:
[[RACSignal combineLatest:@[systolicSignal, diastolicSignal] reduce:^id(DataItemRecordModel *systolicModel, DataItemRecordModel *diastolicModel) {
//reduce中对数据进行处理,可以将多个接口请求的数据,处理之后,统一返回一个结果
return systolicModel;
//也可以将处理完的数据包装成元祖返回
RACTuple *tuple = RACTuplePack(systolicModel, diastolicModel);
return tuple;
}] subscribeNext:^(id _Nullable x) {
//这里获取到reduce处理完成之后的数据
} error:^(NSError * _Nullable error) {
//这里处理错误信息
}];
以上为网络请求接口时的例子,更多的使用方式可以结合(5、事件信号)中的各种信号事件
结尾
2、关于ReactiveObjC原理及流程简介
本文参考:http://blog.csdn.net/mazy_ma/article/details/77151425