iOS开发进阶篇——FRP与ReactiveCocoa的介绍
Posted iOS极限编程
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS开发进阶篇——FRP与ReactiveCocoa的介绍相关的知识,希望对你有一定的参考价值。
*****阅读完此文,大约需要30分钟*****
本篇章是继上文(一)的扩展篇,主要以介绍RAC的常见操作、绑定的概念、RACCommd等相关内容。
一、RACSignal信号的过滤、筛选、合并等常见操作
示例代码如下:
- (void)coldSignalTest
{
//信号的创建
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"被订阅者代码执行");
[[RACScheduler mainThreadScheduler] afterDelay:1 schedule:^{
[subscriber sendNext:@1]; //信号的发送
}];
[[RACScheduler mainThreadScheduler] afterDelay:2 schedule:^{
[subscriber sendNext:@1]; //信号的发送
}];
[[RACScheduler mainThreadScheduler] afterDelay:3 schedule:^{
[subscriber sendNext:@2]; //信号的发送
}];
[[RACScheduler mainThreadScheduler] afterDelay:4 schedule:^{
[subscriber sendNext:@2]; //信号的发送
}];
[[RACScheduler mainThreadScheduler] afterDelay:5 schedule:^{
[subscriber sendNext:@3]; //信号的发送
}];
[[RACScheduler mainThreadScheduler] afterDelay:6 schedule:^{
[subscriber sendCompleted]; //信号的取消订阅
NSLog(@"Signal was ended.");
}];
return nil;
}];
//信号的订阅者
NSLog(@"Signal was created.");
[signal subscribeNext:^(id x) {
NSLog(@"------->%@",x);
}];
}
执行结果如下:
2017-12-19 20:12:13.634289+0800 XYHopeMainProject[62776:5353227] Signal was created.
2017-12-19 20:12:13.635393+0800 XYHopeMainProject[62776:5353227] 被订阅者代码执行
2017-12-19 20:12:14.636535+0800 XYHopeMainProject[62776:5353227] ------->1
2017-12-19 20:12:15.639583+0800 XYHopeMainProject[62776:5353227] ------->1
2017-12-19 20:12:16.639356+0800 XYHopeMainProject[62776:5353227] ------->2
2017-12-19 20:12:17.855557+0800 XYHopeMainProject[62776:5353227] ------->2
2017-12-19 20:12:18.639548+0800 XYHopeMainProject[62776:5353227] ------->3
2017-12-19 20:12:21.754885+0800 XYHopeMainProject[62776:5353227] Signal was ended.
1、信号的过滤、筛选操作
(1)信号的distinctUntilChanged操作
它是将这一次的值与上一次比较,当相同时被忽略掉;
订阅者代码如下:
[[signal distinctUntilChanged] subscribeNext:^(id x) {
NSLog(@"------->%@",x);
}];
执行结果如下:
2017-12-19 20:13:20.663385+0800 XYHopeMainProject[62825:5357099] Signal was created.
2017-12-19 20:13:20.666188+0800 XYHopeMainProject[62825:5357099] 被订阅者代码执行
2017-12-19 20:13:21.672120+0800 XYHopeMainProject[62825:5357099] ------->1
2017-12-19 20:13:23.672064+0800 XYHopeMainProject[62825:5357099] ------->2
2017-12-19 20:13:25.666846+0800 XYHopeMainProject[62825:5357099] ------->3
2017-12-19 20:13:28.671455+0800 XYHopeMainProject[62825:5357099] Signal was ended.
(2)信号的filter操作
它是将原来的Signal经过过滤转化成只返回过滤值的Signal;
订阅者代码如下:
[[signal filter:^BOOL(id value) {
return [value integerValue] == 2;
}] subscribeNext:^(id x) {
NSLog(@"------->%@",x);
}];
执行结果如下:
2017-12-19 20:14:20.225146+0800 XYHopeMainProject[62875:5360908] Signal was created.
2017-12-19 20:14:20.230500+0800 XYHopeMainProject[62875:5360908] 被订阅者代码执行
2017-12-19 20:14:23.236649+0800 XYHopeMainProject[62875:5360908] ------->2
2017-12-19 20:14:24.238931+0800 XYHopeMainProject[62875:5360908] ------->2
2017-12-19 20:14:28.504963+0800 XYHopeMainProject[62875:5360908] Signal was ended.
(3)信号的ignore操作
忽略给定的值。
订阅者代码如下:
[[signal ignore:@2] subscribeNext:^(id x) {
NSLog(@"------->%@",x);
}];
执行结果如下:
2017-12-19 20:15:51.624244+0800 XYHopeMainProject[62940:5366222] Signal was created.
2017-12-19 20:15:51.626976+0800 XYHopeMainProject[62940:5366222] 被订阅者代码执行
2017-12-19 20:15:52.639996+0800 XYHopeMainProject[62940:5366222] ------->1
2017-12-19 20:15:53.636904+0800 XYHopeMainProject[62940:5366222] ------->1
2017-12-19 20:15:56.627526+0800 XYHopeMainProject[62940:5366222] ------->3
2017-12-19 20:16:00.052840+0800 XYHopeMainProject[62940:5366222] Signal was ended.
(4)take和skip操作
take操作是从一开始取N次的值,skip操作是从第N次开始取值;
订阅者代码如下:
[[signal take:2] subscribeNext:^(id x) {
NSLog(@"------->%@",x);
}];
执行结果:
2017-12-19 20:16:46.810426+0800 XYHopeMainProject[62986:5369950] Signal was created.
2017-12-19 20:16:46.812003+0800 XYHopeMainProject[62986:5369950] 被订阅者代码执行
2017-12-19 20:16:47.823175+0800 XYHopeMainProject[62986:5369950] ------->1
2017-12-19 20:16:48.812505+0800 XYHopeMainProject[62986:5369950] ------->1
2017-12-19 20:16:54.872151+0800 XYHopeMainProject[62986:5369950] Signal was ended.
skip操作如下:
[[signal skip:2] subscribeNext:^(id x) {
NSLog(@"------->%@",x);
}];
2017-12-19 20:17:21.309395+0800 XYHopeMainProject[63027:5372134] 被订阅者代码执行
2017-12-19 20:17:24.310024+0800 XYHopeMainProject[63027:5372134] ------->2
2017-12-19 20:17:25.320114+0800 XYHopeMainProject[63027:5372134] ------->2
2017-12-19 20:17:26.320764+0800 XYHopeMainProject[63027:5372134] ------->3
2017-12-19 20:17:29.454537+0800 XYHopeMainProject[63027:5372134] Signal was ended.
(5)takeUntilBlock操作
对于每个signal,执行相应的block,当block返回YES时,停止取值;
订阅者代码如下:
[[signal takeUntilBlock:^BOOL(id x) {
return [x integerValue] == 2;
}] subscribeNext:^(id x) {
NSLog(@"------->%@",x);
}];
执行结果是:
2017-12-19 20:25:28.860173+0800 XYHopeMainProject[63224:5397077] Signal was created.
2017-12-19 20:25:28.864382+0800 XYHopeMainProject[63224:5397077] 被订阅者代码执行
2017-12-19 20:25:29.870062+0800 XYHopeMainProject[63224:5397077] ------->1
2017-12-19 20:25:30.872604+0800 XYHopeMainProject[63224:5397077] ------->1
2017-12-19 20:25:36.870846+0800 XYHopeMainProject[63224:5397077] Signal was ended.
类似的还有:takeUntilSignal、skipUntilBlock、skipUntilSignal、skipWhileBlock等操作;
2、RACSignal信号的组合
(1)信号的merge操作
merge操作就是将多个信号组合成一个信号,当其中一个信号源发送信号时,合成的信号也可以订阅到。
示例代码如下:
- (void)mergeSignalTest
{
//信号1的创建
RACSignal *signal1 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"被订阅者代码执行");
[[RACScheduler mainThreadScheduler] afterDelay:1 schedule:^{
[subscriber sendNext:@1]; //信号的发送
}];
[[RACScheduler mainThreadScheduler] afterDelay:2 schedule:^{
[subscriber sendNext:@2]; //信号的发送
}];
[[RACScheduler mainThreadScheduler] afterDelay:3 schedule:^{
[subscriber sendNext:@3]; //信号的发送
}];
[[RACScheduler mainThreadScheduler] afterDelay:4 schedule:^{
[subscriber sendCompleted]; //信号的取消订阅
NSLog(@"Signal1 was ended.");
}];
return nil;
}];
//信号2的创建
RACSignal *signal2 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"被订阅者代码执行");
[[RACScheduler mainThreadScheduler] afterDelay:4 schedule:^{
[subscriber sendNext:@4]; //信号的发送
}];
[[RACScheduler mainThreadScheduler] afterDelay:5 schedule:^{
[subscriber sendCompleted]; //信号的取消订阅
NSLog(@"Signal2 was ended.");
}];
return nil;
}];
//信号3的创建
RACSignal *signal3 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"被订阅者代码执行");
[[RACScheduler mainThreadScheduler] afterDelay:5 schedule:^{
[subscriber sendNext:@5]; //信号的发送
}];
[[RACScheduler mainThreadScheduler] afterDelay:6 schedule:^{
[subscriber sendCompleted]; //信号的取消订阅
NSLog(@"Signal3 was ended.");
}];
return nil;
}];
[[RACSignal merge:@[signal1, signal2, signal3]] subscribeNext:^(id x) {
NSLog(@"merge操作:%@",x);
}];
}
打印结果如下:
2017-12-21 19:31:45.950506+0800 XYHopeMainProject[45196:9534023] merge操作:1
2017-12-21 19:31:46.950337+0800 XYHopeMainProject[45196:9534023] merge操作:2
2017-12-21 19:31:47.950314+0800 XYHopeMainProject[45196:9534023] merge操作:3
2017-12-21 19:31:48.950434+0800 XYHopeMainProject[45196:9534023] Signal1 was ended.
2017-12-21 19:31:48.950757+0800 XYHopeMainProject[45196:9534023] merge操作:4
2017-12-21 19:31:49.950418+0800 XYHopeMainProject[45196:9534023] Signal2 was ended.
2017-12-21 19:31:49.950736+0800 XYHopeMainProject[45196:9534023] merge操作:5
2017-12-21 19:31:50.950461+0800 XYHopeMainProject[45196:9534023] Signal3 was ended.
(2)信号的combineLatest操作:
combineLatest是合并多个信号源最新的(最后一次)信号,它要求每个信号源都至少发送一次信号,示例代码如下:
[[RACSignal combineLatest:@[signal1, signal2, signal3]] subscribeNext:^(id x) {
NSLog(@"combineLatest操作:%@",x);
}];
执行结果如下:
2017-12-21 19:49:02.999608+0800 XYHopeMainProject[45947:9600534] Signal1 was ended.
2017-12-21 19:49:03.999526+0800 XYHopeMainProject[45947:9600534] Signal2 was ended.
2017-12-21 19:49:04.000250+0800 XYHopeMainProject[45947:9600534] combineLatest操作:<RACTuple: 0x60000001f0d0> (
3,
4,
5
)
2017-12-21 19:49:04.999576+0800 XYHopeMainProject[45947:9600534] Signal3 was ended.
我们看到,三个信号源的最新信号被组合成一个元组,可能元组的数据不太好利用起来,我们还可以对这个合并信号进行包装(reduce)操作,如下:
[[RACSignal combineLatest:@[signal1, signal2, signal3] reduce:^(NSNumber *a, NSNumber *b, NSNumber *c){
return @([a integerValue] + [b integerValue] + [c integerValue]);
}] subscribeNext:^(id x) {
NSLog(@"combineLatest操作:%@",x);
}];
执行结果如下:
2017-12-21 19:51:25.499610+0800 XYHopeMainProject[46043:9608095] Signal1 was ended.
2017-12-21 19:51:26.499504+0800 XYHopeMainProject[46043:9608095] Signal2 was ended.
2017-12-21 19:51:26.501351+0800 XYHopeMainProject[46043:9608095] combineLatest操作:12
2017-12-21 19:51:27.499592+0800 XYHopeMainProject[46043:9608095] Signal3 was ended.
(3)信号的压缩(zip)操作
多个信号之间,可以被压缩zip,压缩的各个信号源的数据被组装成一个元组Tuple返回,而且压缩操作取的是每个信号源的第一次信号的发送,其中某个信号源没发送或者发送多次都会失效,示例代码如下:
[[RACSignal zip:@[signal1, signal2, signal3]] subscribeNext:^(id x) {
NSLog(@"zip操作:%@",x);
}];
打印结果如下:
2017-12-21 19:58:41.871638+0800 XYHopeMainProject[46319:9636728] Signal1 was ended.
2017-12-21 19:58:42.872043+0800 XYHopeMainProject[46319:9636728] Signal2 was ended.
2017-12-21 19:58:42.872621+0800 XYHopeMainProject[46319:9636728] zip操作:<RACTuple: 0x600000012910> (
1,
4,
5
)
2017-12-21 19:58:43.900425+0800 XYHopeMainProject[46319:9636728] Signal3 was ended.
signal1发送了三次信号,只提取了第一次的信号。
如果我们把信号三注释掉,代码:
RACSignal *signal3 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[[RACScheduler mainThreadScheduler] afterDelay:5 schedule:^{
// [subscriber sendNext:@5]; //信号的发送
}];
[[RACScheduler mainThreadScheduler] afterDelay:6 schedule:^{
[subscriber sendCompleted]; //信号的取消订阅
NSLog(@"Signal3 was ended.");
}];
return nil;
}];
执行结果如下:
2017-12-21 20:00:52.694520+0800 XYHopeMainProject[46419:9645358] Signal1 was ended.
2017-12-21 20:00:53.693504+0800 XYHopeMainProject[46419:9645358] Signal2 was ended.
2017-12-21 20:00:54.693506+0800 XYHopeMainProject[46419:9645358] Signal3 was ended.
说明,各个信号源的信号必须至少发送一次信号。
二、双向绑定与RACChannel
1、双向绑定
前面讲到的信号的传递是在发送者和信号接收者之间单向进行的,就是一旦绑定了信号的发送者和接受者,
信号就只会从一方传向另一方。我们可以简单认为这是一种“单向绑定”。事实上RAC还给我们提供了“双向绑定”
的能力,信号的发送者同时也可以是信号的接受者,信号的接受者同时也是信号的发送者,任何一方产生了信号,
另一段都可以收到信号。正如RAC给我解释的那样:
/// Model View
/// `leadingTerminal` ------> `followingTerminal`
/// `leadingTerminal` <------ `followingTerminal`
下面我们看个例子:
下面是由两个输入框构成一个Demo,我们将两个输入框进行双向绑定,任何一个框输入值,另一个框都应该有收到信号:
代码如下:
RACSignal *destorySignal = [[RACSignal merge:@[self.rac_willDeallocSignal]] replay];
RACChannelTerminal *textField1Terminal = [self.myTextField1 rac_newTextChannel];
RACChannelTerminal *textField2Terminal = [self.myTextField2 rac_newTextChannel];
RACDisposable *valueChannelDisposable = [textField1Terminal subscribe:textField2Terminal];
RACDisposable *textFieldChannelDisposable = [textField2Terminal subscribe:textField1Terminal];
[destorySignal subscribeNext:^(id x) {
[valueChannelDisposable dispose];
[textFieldChannelDisposable dispose];
}];
分别对输入框1和2进行输入:
我们发现,另一个框同时也能收到相同的值。当然,上述例子只是纯粹为了演示,实际上,view的是常与model进行双向绑定的。
2、RACChannel
前面的RACChannelTerminal代表了RACChannel的一端,我们看到RACChannel的定义中有两个RACChannelTerminal分别是leadingTerminal
和followingTerminal,可以把它们想象成一根水管的两端,任何一端产生信号,另外一端都会收到相同的信号值。而RACChannelTerminal这个类:
@interface RACChannelTerminal : RACSignal <RACSubscriber>
它继承了RACSignal,因此它具有信号的特性,可以被订阅。同时它又实现了RACSubscriber协议,说明它也可以像订阅者一样可以sendNext:发送信号。
这就是解释了为什么任何一端RACChannelTerminal既可以发送信号,又可以接受信号。我们借用别人的一张图可以形象地描述这一过程:
其中RACChannelTo是一个基于RACKVOChannel的宏定义,RACKVOChannel又是RACChannel的一个子类,RACChannel内部其实是将
一端与相关的keypath上的属性进行绑定(model端),另一端followingT暴露出来(view端)。
我们举个简单的例子,来描述这一过程:
例如我们将输入框1与对应的model绑定起来后,如下:
RACChannelTo(self.myTextField1, text, @"123") = RACChannelTo(self, myTextFieldString1, @"321") ;
分别对myTextFieldString1进行赋值如下:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.myTextFieldString1 = @"10000";
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.myTextFieldString1 = nil;
});
显示结果分别如下:
其中RACChannelTo第三个参数的意义是指如果变化中出现nil,那么就用这个参数来代替。
同时我们改变输入框中输入值,我们在看下myTextFieldString1的变化:
RACChannelTo(self.myTextField1, text) = RACChannelTo(self, myTextFieldString1);
[RACObserve(self, myTextFieldString1) subscribeNext:^(id x) {
NSLog(@"self.myTextFieldString1---->:%@",self.myTextFieldString1);
}];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.myTextField1.text = @"11111";
});
我们也能得到打印结果如下:
self.myTextFieldString1---->:11111
当然RAC也已经帮我们封装了很多的RACChannel拓展:
[UITextField rac_newTextChannel:]
[UISwitch rac_newOnChannel:]
[UISlider rac_newValueChannelWithNilValue:]
[UISegmentedControl rac_newSelectedSegmentIndexChannelWithNilValue:]
[UIDatePicker rac_newDateChannelWithNilValue:]
[NSUserDefaults rac_channelTerminalForKey:]
三、RACCommd
前面介绍了“单向绑定”,“双向绑定”,有人可能会问,有没有介于两者之间的绑定,如果有的话,RACCommd便算是其中一种。
RACCommd常用语处理事件或者Action的执行,常用于网络请求、点击等事件处理。我们直接看一个例子:
- (void)raccommand_test
{
RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
//input为执行命令传进来的参数
NSLog(@"%@",input);
// 这里的返回值不允许为nil
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSString *result = [NSString stringWithFormat:@"网络请求产生的数据 + %@",input];
[subscriber sendNext:result];
return nil;
}];
}];
RACSignal *signal =[command execute:@2];
[signal.switchToLatest subscribeNext:^(id x) {
if (command.executing) {
NSLog(@"%@",x);
}
}];
}
执行打印结果如下:
2018-01-03 12:02:12.624953+0800 XYHopeMainProject[91178:28862356] 网络请求产生的数据 + 2
我们在红色部门构造网络请求,在后面通过execute:执行,并传入请求参数,网络请求部分拿到传参,进行网络请求,结果sendNext:返回给订阅者。
RACCommd可以将一些复杂的操作进行封装,通过自身的execute:、executing等操作来控制执行。通过这些操作,我们可以方便地控制当前封装的操作
是否可以执行、多次执行、是否正在执行、返回数据的订阅(RACSignal)等;总之,通过不同的输入,有着不同的输入;
通过以上我们也可以看出,RACCommd不同前面的RACChannel,它不是一种完全的“双向绑定”,它的绑定双方不对等,勉强算是一种“半双向绑定”
当然,这种绑定与MVVM的结合使用,将会十分的便捷,后续会继续介绍。
四、参考文档
https://tech.meituan.com/ReactiveCocoaSignalFlow.html
http://blog.csdn.net/majiakun1/article/details/52937770
https://www.jianshu.com/p/dc472c644e7b
https://www.jianshu.com/p/1a0185782d8a
https://draveness.me/raccommand
以上是关于iOS开发进阶篇——FRP与ReactiveCocoa的介绍的主要内容,如果未能解决你的问题,请参考以下文章