随着各种Rx的开始流行,做IT的码农门不得不学习各种新的知识,不然没有逼格,没办法在老板面前提涨工资啊啊啊啊啊。所以这篇文章我就记录一下我学习ReactiveCocoa的常见方法,作参考使用。
在ReactiveCocoa中,关键是理解Signal流,所有的信息传递都是以流的形式
常见的宏定义
RAC(TRAGET,[KEYPATH,[NIL_VALUE]])
RAC(self.outputLabel, text) = self.inputTextField.rac_textSignal;
RAC(self.outputLabel, text, @"收到nil时就显示我") = self.inputTextField.rac_textSignal;
这个宏是最常用的,RAC()总是出现在等号的左边,右边是一个signal,表示将一个signal和一个对象的属性绑定,signal每一次更新都会自动执行:[TARGET setValue:value ?: NUL_VALUE forkeyPath:KEYPATH]
RACObserver(TARGET,KEYPATH)
产生一个signal,当keypath有改变就更新。例如:
RAC(self.outputLabel, text) = RACObserve(self.model, name);
当model的name属性发生改变都会反映到label的text上
还有另外两个宏
@weakify(self)
, @strongify(self)
二者需要成对出现,先weak 后strong
创建信号
RACSignal * signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"0000");
[subscriber sendNext:@1];// 如果 注释掉subscribeNext就不会发送
NSLog(@"1111");
[subscriber sendCompleted];
NSLog(@"2222");
return [RACDisposable disposableWithBlock:^{
// block调用时刻:当信号发送完成或者发送错误,就会自动执行这个block,取消订阅信号。
// 执行完Block后,当前信号就不在被订阅了。
NSLog(@"信号被销毁");
}];
}];
信号只有被订阅才能触发
// 3.订阅信号,才会激活信号.
[signal subscribeNext:^(id x) {
// block调用时刻:每当有信号发出数据,就会调用block.
NSLog(@"接收到数据:%@",x);
}];
[signal subscribeNext:^(id x) {
// block调用时刻:每当有信号发出数据,就会调用block.
NSLog(@"2接收到数据:%@",x);
}];
注意:这里有一个副作用,每次subscribeNext 就会触发一次subscriber的block。
0000 1111 2222 被调用两次 可以使用RACMulticastConnection 进行解决
RACObserver(owner,Property)
RACSignal * signal = [RACObserve(self, model.name) map:^id(id value) {
NSLog(@"value =%@",value);
return value ? @YES : @NO;
}];
[signal subscribeNext:^(id x) {
NSLog(@"xxxxxx =%@",x);
}];
使用KVO,监听属性的变化,值的任何改变都会反映出来
我们可以看一下源代码:
#define RACObserve(TARGET, KEYPATH) \\
({ \\
_Pragma("clang diagnostic push") \\
_Pragma("clang diagnostic ignored \\"-Wreceiver-is-weak\\"") \\
__weak id target_ = (TARGET); \\
[target_ rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self]; \\
_Pragma("clang diagnostic pop") \\
})
rac_valueForKeyPath: observer:
该方法的实现是在NSObject (RACPropertySubscribing)
类中
RACSubject
既可以充当订阅者也可以充当信息发送者
RACSubject *subject = [RACSubject subject];
// 2.订阅信号
[subject subscribeNext:^(id x) {
NSLog(@"第一个订阅者%@",x);
}];
[subject subscribeNext:^(id x) {
NSLog(@"第二个订阅者%@",x);
}];
// 3.发送信号
[subject sendNext:@"100"];
[subject sendNext:@"200"];
这里也是需要注意的是:每次subscribeNext都会产生一个subscriber
打印输出如下:
2016-02-18 14:05:18.030 JFReactive[19899:1936775] begin
2016-02-18 14:05:18.030 JFReactive[19899:1936775] subscribers = <RACPassthroughSubscriber: 0x7f8be0ec5730>
2016-02-18 14:05:18.031 JFReactive[19899:1936775] 第一个订阅者100
2016-02-18 14:05:18.031 JFReactive[19899:1936775] subscribers = <RACPassthroughSubscriber: 0x7f8be0eaf310>
2016-02-18 14:05:18.031 JFReactive[19899:1936775] 第二个订阅者100
2016-02-18 14:05:18.031 JFReactive[19899:1936775] end
2016-02-18 14:05:18.031 JFReactive[19899:1936775] begin
2016-02-18 14:05:18.031 JFReactive[19899:1936775] subscribers = <RACPassthroughSubscriber: 0x7f8be0ec5730>
2016-02-18 14:05:18.032 JFReactive[19899:1936775] 第一个订阅者200
2016-02-18 14:05:18.032 JFReactive[19899:1936775] subscribers = <RACPassthroughSubscriber: 0x7f8be0eaf310>
2016-02-18 14:05:18.032 JFReactive[19899:1936775] 第二个订阅者200
2016-02-18 14:05:18.032 JFReactive[19899:1936775] end
RACSubject 实现代理:
RACReplaySubject
重复发送之前保存的数据
// 1.创建信号
RACReplaySubject *replaySubject = [RACReplaySubject subject];
// 2.发送信号
[replaySubject sendNext:@1];
[replaySubject sendNext:@2];
// 3.订阅信号
[replaySubject subscribeNext:^(id x) {// 创建第一个subscriber
NSLog(@"第一个订阅者接收到的数据%@",x);
}];
// 订阅信号
[replaySubject subscribeNext:^(id x) {// 创建第二个subscriber
NSLog(@"第二个订阅者接收到的数据%@",x);
}];
[replaySubject sendCompleted];//
输出如下:
如果sendNext发生在 subscribeNext之前就打印如下
2015-12-26 17:29:07.467 JFReactive[60987:2909146] 第一个订阅者接收到的数据1
2015-12-26 17:29:07.467 JFReactive[60987:2909146] 第一个订阅者接收到的数据2
2015-12-26 17:29:07.468 JFReactive[60987:2909146] 第二个订阅者接收到的数据1
2015-12-26 17:29:07.468 JFReactive[60987:2909146] 第二个订阅者接收到的数据2
如果是发生在之后
2016-01-04 16:46:28.693 JFReactive[32869:4260996] 第一个订阅者接收到的数据1
2016-01-04 16:46:28.693 JFReactive[32869:4260996] 第二个订阅者接收到的数据1
2016-01-04 16:46:28.694 JFReactive[32869:4260996] 第一个订阅者接收到的数据2
2016-01-04 16:46:28.696 JFReactive[32869:4260996] 第二个订阅者接收到的数据2
序列 Sequence
一般情况下我们遍历一个序列使用for循环,现在有了RAC,简单了很多哦
NSArray *numberSequnence = @[@1,@2,@3,@4];
[numberSequnence.rac_sequence.signal subscribeNext:^(id x) {
NSLog(@"thread = %@",[NSThread currentThread]);
NSLog(@"main thread = %@",[NSThread mainThread]);
NSLog(@"%@",x);
}];
输出如下:
2016-02-18 15:49:25.223 JFReactive[21263:1981822] create subscribers = <RACSubscriber: 0x7f916af19160>
2016-02-18 15:49:25.225 JFReactive[21263:1982967] thread = <NSThread: 0x7f916ad97680>{number = 2, name = (null)}
2016-02-18 15:49:25.226 JFReactive[21263:1982967] main thread = <NSThread: 0x7f916ad05ca0>{number = 1, name = (null)}
2016-02-18 15:49:25.226 JFReactive[21263:1982967] 1
2016-02-18 15:49:25.227 JFReactive[21263:1982967] thread = <NSThread: 0x7f916ad97680>{number = 2, name = (null)}
2016-02-18 15:49:25.227 JFReactive[21263:1982967] main thread = <NSThread: 0x7f916ad05ca0>{number = 1, name = (null)}
2016-02-18 15:49:25.227 JFReactive[21263:1982967] 2
2016-02-18 15:49:25.227 JFReactive[21263:1982967] thread = <NSThread: 0x7f916ad97680>{number = 2, name = (null)}
2016-02-18 15:49:25.228 JFReactive[21263:1982967] main thread = <NSThread: 0x7f916ad05ca0>{number = 1, name = (null)}
2016-02-18 15:49:25.228 JFReactive[21263:1982967] 3
2016-02-18 15:49:25.228 JFReactive[21263:1982967] thread = <NSThread: 0x7f916ad97680>{number = 2, name = (null)}
2016-02-18 15:49:25.228 JFReactive[21263:1982967] main thread = <NSThread: 0x7f916ad05ca0>{number = 1, name = (null)}
2016-02-18 15:49:25.228 JFReactive[21263:1982967] 4
我们会发现遍历序列是开启了一个线程,并不在主线程中
接下来我们看看遍历字典的例子
NSDictionary *dict = @{@"name":@"sun",@"age":@18};
[dict.rac_sequence.signal subscribeNext:^(RACTuple *x) {
// 解包元组,会把元组的值,按顺序给参数里面的变量赋值
RACTupleUnpack(NSString *key,NSString *value) = x;
NSString *keys = x[0];
NSString *values = x[1];
NSLog(@"%@ %@",key,value);
NSLog(@"thread = %@",[NSThread currentThread]);
NSLog(@"main thread = %@",[NSThread mainThread]);
}];
同样,也不在主线程中
Command
command 手动执行需要excute方法,但是和button绑定后不需要手动执行,因为button有相应的rx_command
在这里引用一篇blog中的说明
// 一、RACCommand使用步骤:
// 1.创建命令 initWithSignalBlock:(RACSignal * (^)(id input))signalBlock
// 2.在signalBlock中,创建RACSignal,并且作为signalBlock的返回值
// 3.执行命令 - (RACSignal *)execute:(id)input
// 二、RACCommand使用注意:
// 1.signalBlock必须要返回一个信号,不能传nil.
// 2.如果不想要传递信号,直接创建空的信号[RACSignal empty];
// 3.RACCommand中信号如果数据传递完,必须调用[subscriber sendCompleted],这时命令才会执行完毕,否则永远处于执行中。
// 4.RACCommand需要被强引用,否则接收不到RACCommand中的信号,因此RACCommand中的信号是延迟发送的。
// 三、RACCommand设计思想:内部signalBlock为什么要返回一个信号,这个信号有什么用。
// 1.在RAC开发中,通常会把网络请求封装到RACCommand,直接执行某个RACCommand就能发送请求。
// 2.当RACCommand内部请求到数据的时候,需要把请求的数据传递给外界,这时候就需要通过signalBlock返回的信号传递了。
// 四、如何拿到RACCommand中返回信号发出的数据。
// 1.RACCommand有个执行信号源executionSignals,这个是signal of signals(信号的信号),意思是信号发出的数据是信号,不是普通的类型。
// 2.订阅executionSignals就能拿到RACCommand中返回的信号,然后订阅signalBlock返回的信号,就能获取发出的值。
// 五、监听当前命令是否正在执行executing
// 六、使用场景,监听按钮点击,网络请求
// 1.创建命令
RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
NSLog(@"执行命令");
NSLog(@"input =%@",input);
// 创建空信号,必须返回信号
// return [RACSignal empty];
// 2.创建信号,用来传递数据
RACSignal * rSignal =[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"请求数据"];
// 注意:数据传递完,最好调用sendCompleted,这时命令才执行完毕。
[subscriber sendCompleted];
return nil;
}];
NSLog(@"innerSiganl = %@",rSignal);
return rSignal;
}];
// 强引用命令,不要被销毁,否则接收不到数据
_command = command;
// 3.订阅RACCommand中的信号
//RACCommand有个执行信号源executionSignals,这个是signal of signals(信号的信号),意思是信号发出的数据是信号,不是普通的类型。
// 2.订阅executionSignals就能拿到RACCommand中返回的信号,然后订阅signalBlock返回的信号,就能获取发出的值。
[command.executionSignals subscribeNext:^(id x) {
NSLog(@"subscribe signal %@",x);//返回signal
[x subscribeNext:^(id x) {
NSLog(@"singnal's next %@",x);//返回signal的数据
}];
}];
// 4.监听命令是否执行完毕,默认会先触发 正在执行 x= yes,可以直接跳过,skip表示跳过第一次信号。
[[command.executing skip:1] subscribeNext:^(id x) {
// skip 跳过外部的信号的执行处理,只处理command内部信号,所以只执行了2次 1 ,0
NSLog(@"excuting skip xx = %@",x);
if ([x boolValue] == YES) {
// 正在执行
NSLog(@"正在执行");
}else{
// 执行完成
NSLog(@"执行完成");
}
}];
[command.executing subscribeNext:^(id x) {
NSLog(@"excuting next :%@",x);// 输出 0 1 0 三次
}];
// 5.执行命令
[self.command execute:@100];// 无此语句则无法触发所有的command
从上述描述中可知 executionSignals 是一个信号中的信号
还有其他的方法:
switchToLatest
切换到最新的信号
skip(count)
忽略count个信号
输出结果:
2015-12-26 18:48:23.728 JFReactive[61623:2932153] excuting next :0
2015-12-26 18:48:23.728 JFReactive[61623:2932153] 执行命令
2015-12-26 18:48:23.728 JFReactive[61623:2932153] input =100
2015-12-26 18:48:23.729 JFReactive[61623:2932153] excuting xx = 1
2015-12-26 18:48:23.729 JFReactive[61623:2932153] 正在执行
2015-12-26 18:48:23.730 JFReactive[61623:2932153] excuting next :1
2015-12-26 18:48:23.730 JFReactive[61623:2932153] subscribe signal <RACDynamicSignal: 0x7fa053437f20> name:
2015-12-26 18:48:23.730 JFReactive[61623:2932153] singnal's next 请求数据
2015-12-26 18:48:23.730 JFReactive[61623:2932153] switchToLast subnext: 请求数据
2015-12-26 18:48:23.731 JFReactive[61623:2932153] excuting xx = 0
2015-12-26 18:48:23.731 JFReactive[61623:2932153] 执行完成
2015-12-26 18:48:23.731 JFReactive[61623:2932153] excuting next :0
Map 和flattenMap
FlatternMap和Map的区别
1.FlatternMap中的Block返回信号。
2.Map中的Block返回对象。
3.开发中,如果信号发出的值不是信号,映射一般使用Map
如果信号发出的值是信号,映射一般使用FlatternMap。
flattenMap 在map的基础上使其flatten,也就是当Signal嵌套(一个Signal的事件是另一个Signal)的时候,会将内部Signal的事件传递给外部Signal
RACMulticastConnection
解决signal 每次订阅subscribeNext 就出发一次 sendNext
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"发送请求");
[subscriber sendNext:@"200"];
return nil;
}];
// 使用muticastConnection 替代 解决 了多个订阅,发多次请求的问题
RACMulticastConnection *connect = [signal publish];
// 3.订阅信号,
// 注意:订阅信号,也不能激活信号,只是保存订阅者到数组,必须通过连接,当调用连接,就会一次性调用所有订阅者的sendNext:
//
[connect.signal subscribeNext:^(id x) {
NSLog(@"订阅者一信号 %@",x);
}];
[connect.signal subscribeNext:^(id x) {
NSLog(@"订阅者二信号 %@",x);
}];
// 4.连接,激活信号
[connect connect];
打印输出:
2016-02-18 22:00:30.238 JFReactive[24275:2098877] 发送请求
2016-02-18 22:00:30.238 JFReactive[24275:2098877] 订阅者一信号 200
2016-02-18 22:00:30.238 JFReactive[24275:2098877] 订阅者二信号 200
rac_liftSelector
#pragma mark 多个信号统一处理 初始时必须两个信号均发送成功,然后任意信号发送都会触发,发送error 都不会触发selector
- (void)multiSignalWithSelector{
// 6.处理多个请求,都返回结果的时候,统一做处理.
RACSignal *request1 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// 发送请求1
[subscriber sendNext:@"发送请求1"];
//[subscriber sendError:nil];
return nil;
}];
RACSignal *request2 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// 发送请求2
[subscriber sendNext:@"发送请求2"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subscriber sendNext:@"发送请求2"];
});
return nil;
}];
// 使用注意:几个信号,参数一的方法就几个参数,每个参数对应信号发出的数据。
[self rac_liftSelector:@selector(updateUIWithR1:r2:) withSignalsFromArray:@[request1,request2]];
}
- (void)updateUIWithR1:(id)data r2:(id)data1
{
NSLog(@"更新UI%@ %@",data,data1);
}
bind
该方法适用于提前终止绑定或者覆盖状态,否则最好使用flattenMap
[[_textField.rac_textSignal bind:^RACStreamBindBlock{
return ^RACStream *(id value, BOOL *stop){
//当信号有新的值发出,就会来调用这个block。
NSLog(@"%d",*stop);
if ([value isEqualToString:@"fff"]) {
*stop = YES;
}
return [RACReturnSignal return:[NSString stringWithFormat:@"value:%@",value]];
};
}] subscribeNext:^(id x) {
NSLog(@"输出XXX %@",x);///当stop = yes 后不再输出
}];
参考:
http://www.cnblogs.com/sunnyxx/p/3544703.html
http://www.jianshu.com/p/87ef6720a096
RACCommand例子:
http://www.tuicool.com/articles/nYJRvu