我在上一篇博客中《响应式编程框架ReactiveCocoa介绍与入门》简单介绍了ReactiveCocoa的介绍和简单使用,主要是翻译了官方文档中的README部分,其实个人认为技术最好的学习方式就是去看官方文档。今天我仍旧来翻译官方文档中的BasicOperators部分,也就是基本操作符。我写的一部分代码示例上传至 https://github.com/chenyufeng1991/ReactiveCocoaDemo,欢迎大家下载查看。
这篇文档主要讲解了在ReactiveCocoa使用中常见的操作符,包括它们如何使用的例子。运用到sequence和signal中的被认为是一种流操作符。
【使用信号的副作用】
大多数信号是一种冷信号(cold),也就是说它们不会做任何操作,直到它们被订阅(subscription)。当被订阅以后,信号和订阅者可以使用副作用,比如控制台打印日志,网络请求,或者更新用户界面等等。副作用也可以被注入到信号中,可以让这个信号不是立即执行,而是到每一个订阅发生后才去执行。
1.订阅(Subscription)
-subscribe...方法用来访问信号中当前或未来的值。如下示例:
RACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal; // Outputs: A B C D E F G H I [letters subscribeNext:^(NSString *x) { NSLog(@"%@",x); }];结果会打印出A B C D E F G H I. 对于冷信号,副作用将会在每次订阅的时候都会发生。如下示例:
__block unsigned subscriptions = 0; RACSignal *loggingSignal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { subscriptions++; [subscriber sendCompleted]; return nil; }]; // Outputs: // subscription 1 [loggingSignal subscribeCompleted:^{ NSLog(@"subscription %u",subscriptions); }]; // Outputs: // subscription 2 [loggingSignal subscribeCompleted:^{ NSLog(@"subscription %u",subscriptions); }];
第一次订阅的时候也就是调用subscribeComplete,会输出subscription 1,第二次调用subscribeComplete的时候也就是第二次订阅,会输出subscription 2. 如果不想使用这种副作用的特性,可以使用connect,后面会讲到。
2.注入影响
-do...方法就算在没有订阅的时候也能给信号添加副作用,如下代码:
__block unsigned subscriptions = 0; RACSignal *loggingSignal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { subscriptions++; [subscriber sendCompleted]; return nil; }]; // Does not output anything yet loggingSignal = [loggingSignal doCompleted:^{ NSLog(@"about to complete subscription %u",subscriptions); }]; // Outputs: // about to complete subscription 1 // subscription 1 [loggingSignal subscribeCompleted:^{ NSLog(@"subscription %u",subscriptions); }];
下面的这些操作可以转变一个流到另一种流。
1.Mapping 映射
-map:方法传输值到流中,然后创建一个新的流作为结果:
RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence; // Contains: AA BB CC DD EE FF GG HH II RACSequence *mapped = [letters map:^(NSString *value) { return [value stringByAppendingString:value]; }];输出内容为"AA BB CC DD EE FF GG HH II".
2.Filtering 过滤
-filter方法用来测试block中的值,只有当符合要求测试通过才可以作为结果流输出。
RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence; // Contains: 2 4 6 8 RACSequence *filtered = [numbers filter:^ BOOL (NSString *value) { return (value.intValue % 2) == 0; }];
【组合流】
以下这些操作符可以组合多个流成为一个新的流输出。
1.Concatenating 连接
RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence; RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence; // Contains: A B C D E F G H I 1 2 3 4 5 6 7 8 9 RACSequence *concatenated = [letters concat:numbers];输出结果:A B C D E F G H I 1 2 3 4 5 6 7 8 9 .
2.Flattening 降阶
-flatten 操作符可以运用到“流的流”,结合它们的值作为新的流。
序列的连接 :
RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence; RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence; RACSequence *sequenceOfSequences = @[ letters,numbers ].rac_sequence; // Contains: A B C D E F G H I 1 2 3 4 5 6 7 8 9 RACSequence *flattened = [sequenceOfSequences flatten];代码中可以看到,本来sequenceOfSequence是“序列的序列”,也就是流的流。是letters和numbers两种流的组合,然后使用flatten方法来降阶为新的流。
信号的合并:
RACSubject *letters = [RACSubject subject]; RACSubject *numbers = [RACSubject subject]; RACSignal *signalOfSignals = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { [subscriber sendNext:letters]; [subscriber sendNext:numbers]; [subscriber sendCompleted]; return nil; }]; RACSignal *flattened = [signalOfSignals flatten]; // Outputs: A 1 B C 2 [flattened subscribeNext:^(NSString *x) { NSLog(@"%@",x); }]; [letters sendNext:@"A"]; [numbers sendNext:@"1"]; [letters sendNext:@"B"]; [letters sendNext:@"C"]; [numbers sendNext:@"2"];代码中的signalOfSignal是letters和numbers两个信号的合并,然后通过flatten操作降阶,然后在转变成flattened这个信号,当letters和numbers这两个信号分别发送数据的时候,降阶后的flattened信号就能接收到。
3.Mapping and flattening 映射和降阶
Flattening操作最重要的是去理解在-FlattenMap中的使用。
-flattenMap:是用来转变每一个流的值成为一个新的流。因此,所有流的返回可以返回降阶为新的流。换句话说,先执行-flatten操作,再执行-map操作。这可以用来扩展和编辑序列:
RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence; // Contains: 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 RACSequence *extended = [numbers flattenMap:^(NSString *num) { return @[ num,num ].rac_sequence; }]; // Contains: 1_ 3_ 5_ 7_ 9_ RACSequence *edited = [numbers flattenMap:^(NSString *num) { if (num.intValue % 2 == 0) { return [RACSequence empty]; } else { NSString *newNum = [num stringByAppendingString:@"_"]; return [RACSequence return:newNum]; } }];
RACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal; [[letters flattenMap:^(NSString *letter) { return [database saveEntriesForLetter:letter]; }] subscribeCompleted:^{ NSLog(@"All database entries saved successfully."); }];
【组合信号】
以下的操作符可以把多个信号组合成新的RACSignal信号。
1.序列化
-then:开始于一个原始的信号,并等待它完成,然后转发到新的信号。
RACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal; // The new signal only contains: 1 2 3 4 5 6 7 8 9 // // But when subscribed to,it also outputs: A B C D E F G H I RACSignal *sequenced = [[letters doNext:^(NSString *letter) { NSLog(@"%@",letter); }] then:^{ return [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence.signal; }];这种方式用来处理信号的副作用非常有用,开始于一个信号,但是返回的却是第二个信号的值。
2.Merging 合并
+merge:方法会把多个信号的到达的值转发为一个信号,如下:
RACSubject *letters = [RACSubject subject]; RACSubject *numbers = [RACSubject subject]; RACSignal *merged = [RACSignal merge:@[ letters,numbers ]]; // Outputs: A 1 B C 2 [merged subscribeNext:^(NSString *x) { NSLog(@"%@",x); }]; [letters sendNext:@"A"]; [numbers sendNext:@"1"]; [letters sendNext:@"B"]; [letters sendNext:@"C"]; [numbers sendNext:@"2"];
3.Combining latest values 组合最新值
+combineLatest:方法和+combineLatest:reduce:方法可以监听多个信号值的改变,当这些信号中的值改变使就能发送最新值。
RACSubject *letters = [RACSubject subject]; RACSubject *numbers = [RACSubject subject]; RACSignal *combined = [RACSignal combineLatest:@[ letters,numbers ] reduce:^(NSString *letter,NSString *number) { return [letter stringByAppendingString:number]; }]; // Outputs: B1 B2 C2 C3 [combined subscribeNext:^(id x) { NSLog(@"%@",x); }]; [letters sendNext:@"A"]; [letters sendNext:@"B"]; [numbers sendNext:@"1"]; [numbers sendNext:@"2"]; [letters sendNext:@"C"]; [numbers sendNext:@"3"];输出结果:“B1 B2 C2 C3”,这里的输出优点奇怪,我们来分析一下。可以看到组合信号只会在每一个信号至少发送一次后才会去发送第一个值。在例子中,@"A"始终没有被打印是因为前面没有接收到numbers信号。
4.Switching 切换
-switchToLatest操作符运用到“信号的信号”,用来转发最新信号中的值。
RACSubject *letters = [RACSubject subject]; RACSubject *numbers = [RACSubject subject]; RACSubject *signalOfSignals = [RACSubject subject]; RACSignal *switched = [signalOfSignals switchToLatest]; // Outputs: A B 1 D [switched subscribeNext:^(NSString *x) { NSLog(@"%@",x); }]; [signalOfSignals sendNext:letters]; [letters sendNext:@"A"]; [letters sendNext:@"B"]; [signalOfSignals sendNext:numbers]; [letters sendNext:@"C"]; [numbers sendNext:@"1"]; [signalOfSignals sendNext:letters]; [numbers sendNext:@"2"]; [letters sendNext:@"D"];
来简单分析一下结果,这里会打印出“A B 1 D”. switchToLatest是对于信号而言的,会让switched这个信号切换到最新改变的那个信号。当第一次sendNext:letters时,表示切换到letters信号,此时最新的就是letters信号,所以可以打印出A B。 然后第二次sendNext:numbers时,表示当前改变的最新信号是numbers信号,所以当发送C的时候其实我是接收不到的,所以不会打印C,只会打印1。第三次时的情况和第二次则是类似的。
本文大部分翻译自 https://github.com/ReactiveCocoa/ReactiveCocoa/blob/v2.5/Documentation/BasicOperators.md