文是对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<RACSubscriber>_Nonnull subscriber){//任何时候,都可以发送信号,可以异步[subscriber sendNext:@"发送信号"];//数据传递完,最好调用sendCompleted,这时命令才执行完毕。[subscriber sendCompleted];returnnil;}];//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){returnvalue.length>5;}]subscribeNext:^(NSString*_Nullable x){NSLog(@"filter result = %@",x);}];
3、ignore 对订阅的信号进行过滤
[[[self.textField rac_textSignal]ignore:@"666"]subscribeNext:^(NSString*_Nullable x){//当输入的内容 equalTo @"666" 时,这里不执行//其他内容,均会执行subscribeNextNSLog(@"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.0fonScheduler:[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<RACSubscriber>_Nonnull subscriber){[subscriber sendNext:@1];returnnil;}]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方法中,给信号发送消息
@interfaceDelegateView:UIView//定义了一个RACSubject信号@property(nonatomic,strong)RACSubject*delegateSignal;@end@implementationDelegateView-(void)touchesBegan:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event{// 判断代理信号是否有值if(self.delegateSignal){// 有值,给信号发送消息[self.delegateSignal sendNext:@666];}}@end
在UIViewController中声明DelegateView作为属性
@interfaceViewController()@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视图的背景颜色变为grayColorRAC(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:表格排版加上<br>换行之后,才不至于列的内容挤到一起,累累累...
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[RACSignalreturn:[NSString stringWithFormat:@"hello: %@",value]];};}]subscribeNext:^(id _Nullable x){NSLog(@"bind : %@",x);// hello: "x"}];
3、concat 合并
按一定顺序拼接信号,当多个信号发出的时候,有顺序的接收信号
RACSignal*signalA=[RACSignal createSignal:^RACDisposable*_Nullable(id<RACSubscriber>_Nonnull subscriber){[subscriber sendNext:@"signalA"];[subscriber sendCompleted];returnnil;}];RACSignal*signalB=[RACSignal createSignal:^RACDisposable*_Nullable(id<RACSubscriber>_Nonnull subscriber){[subscriber sendNext:@"signalB"];[subscriber sendCompleted];returnnil;}];// 把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<RACSubscriber>_Nonnull subscriber){[subscriber sendNext:@1];[subscriber sendCompleted];returnnil;}]then:^RACSignal*{return[RACSignal createSignal:^RACDisposable*_Nullable(id<RACSubscriber>_Nonnull subscriber){//可以对第一个信号的数据进行过滤处理 , 不能直接获得第一个信号的数据返回值[subscriber sendNext:@2];returnnil;}];}]subscribeNext:^(id x){// 只能接收到第二个信号的值,也就是then返回信号的值NSLog(@"then : %@",x);// 2}];
5、merge 合并
把多个信号合并为一个信号,任何一个信号有新值的时候就会调用
//创建多个信号RACSignal*mergeSignalA=[RACSignal createSignal:^RACDisposable*_Nullable(id<RACSubscriber>_Nonnull subscriber){[subscriber sendNext:@1];returnnil;}];RACSignal*mergeSignalB=[RACSignal createSignal:^RACDisposable*_Nullable(id<RACSubscriber>_Nonnull subscriber){[subscriber sendNext:@2];returnnil;}];// 合并信号,只要有信号发送数据,都能监听到.RACSignal*mergeSignal=[mergeSignalA merge:mergeSignalB];[mergeSignal subscribeNext:^(id x){//每次获取单个信号的值NSLog(@"merge : %@",x);}];
6、zipWith 压缩
把两个信号压缩成一个信号,只有当两个信号都发出一次信号内容后,并且把两个信号的内容合并成一个元组,才会触发压缩流的next事件(组合的数据都是一一对应的)
RACSignal*zipSignalA=[RACSignal createSignal:^RACDisposable*_Nullable(id<RACSubscriber>_Nonnull subscriber){[subscriber sendNext:@1];[subscriber sendNext:@2];returnnil;}];RACSignal*zipSignalB=[RACSignal createSignal:^RACDisposable*_Nullable(id<RACSubscriber>_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];});returnnil;}];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<RACSubscriber>_Nonnull subscriber){[subscriber sendNext:@1];[subscriber sendNext:@2];returnnil;}];RACSignal*combineSignalB=[RACSignal createSignal:^RACDisposable*_Nullable(id<RACSubscriber>_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];});returnnil;}];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<RACSubscriber>_Nonnull subscriber){[subscriber sendNext:@1];returnnil;}];RACSignal*reduceSignalB=[RACSignal createSignal:^RACDisposable*_Nullable(id<RACSubscriber>_Nonnull subscriber){[subscriber sendNext:@3];returnnil;}];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[RACSignalreturn:[NSString stringWithFormat:@"hello : %@",value]];}]subscribeNext:^(id _Nullable x){NSLog(@"flattenMap : %@",x);// hello "x"}];
10、filter 过滤
过滤信号,获取满足条件的信号
[[self.textField.rac_textSignal filter:^BOOL(NSString*value){returnvalue.length>6;}]subscribeNext:^(NSString*_Nullable x){NSLog(@"filter : %@",x);// x 值位数大于6}];
六、结合网络请求使用
以下网络接口均基于MVVM模式
1、请求单个接口
//创建请求接口的信号,该方法可以定义并实现在ViewModel层#pragmamark - 获取指定时间的课程+(RACSignal*)getCourseInfoByTime:(NSInteger)time{//此处为接口所需参数NSMutableDictionary*paramter=[NSMutableDictionary dictionary];[paramter setObject:@(time)forKey:@"exerciseTime"];return[RACSignal createSignal:^RACDisposable*(id<RACSubscriber>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];}}];returnnil;}];}//信号订阅//在ViewController中定义一个方法,用来调用网络接口方法-(void)getCourseByIsExperience:(BOOL)isExperience{//使用 @weakify(self) 和 @strongify(self) 避免循环引用@weakify(self)[[SportViewModel getCourseInfoByTime:0isExperience: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)//没有数据[selfhandleTheErrorMessage:error];}];
多个接口同时调用的过程同单个接口请求类似。
需注意:
(一)多个接口同时请求时,只要有其中一个返回错误信息,整个结果即为失败,即会走error:^(NSError * _Nullable error){}这个block,所以必须多个接口都成功时,才会调用subscribeNext:^(RACTuple * _Nullable x){}block。
(二)可以对结果先做聚合处理,返回再返回结果,比如:
[[RACSignal combineLatest:@[systolicSignal,diastolicSignal]reduce:^id(DataItemRecordModel*systolicModel,DataItemRecordModel*diastolicModel){//reduce中对数据进行处理,可以将多个接口请求的数据,处理之后,统一返回一个结果returnsystolicModel;//也可以将处理完的数据包装成元祖返回RACTuple*tuple=RACTuplePack(systolicModel,diastolicModel);returntuple;}]subscribeNext:^(id _Nullable x){//这里获取到reduce处理完成之后的数据}error:^(NSError*_Nullable error){//这里处理错误信息}];
以上为网络请求接口时的例子,更多的使用方式可以结合(5、事件信号)中的各种信号事件😆