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进行输入:

iOS开发进阶篇——FRP与ReactiveCocoa的介绍(二)

iOS开发进阶篇——FRP与ReactiveCocoa的介绍(二)

我们发现,另一个框同时也能收到相同的值。当然,上述例子只是纯粹为了演示,实际上,view的是常与model进行双向绑定的。

2、RACChannel

前面的RACChannelTerminal代表了RACChannel的一端,我们看到RACChannel的定义中有两个RACChannelTerminal分别是leadingTerminal

和followingTerminal,可以把它们想象成一根水管的两端,任何一端产生信号,另外一端都会收到相同的信号值。而RACChannelTerminal这个类:

@interface RACChannelTerminal : RACSignal <RACSubscriber>

它继承了RACSignal,因此它具有信号的特性,可以被订阅。同时它又实现了RACSubscriber协议,说明它也可以像订阅者一样可以sendNext:发送信号。

这就是解释了为什么任何一端RACChannelTerminal既可以发送信号,又可以接受信号。我们借用别人的一张图可以形象地描述这一过程:

iOS开发进阶篇——FRP与ReactiveCocoa的介绍(二)

其中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的介绍的主要内容,如果未能解决你的问题,请参考以下文章

iOS 开发中的 MVVM 模式——实用进阶篇(整理)

iOS开发之进阶篇(15)—— CocoaPods

鹅厂6年在职架构师告诉你如何成为iOS大牛开发者进阶篇

iOS开发者如何在枯燥的工作中寻求技术的提升(进阶篇)

iOS开发 - 第02篇 - UI进阶 - 08 - 私人通讯录

浅谈月薪3万 iOS程序员 的职业规划与成长!(进阶篇)