探究RAC-RAC信号处理方法归纳

继续完成RAC的坑


前言

经过上篇文章的学习,相信不少读者已经熟悉了RAC的基本用法,可以尝试在以后的开发中不用add target语句了。这篇文章将稍微深入一点了解RAC的更多功能以及适用场合。

什么是信号

RAC的核心就是信号,即RACSignal

信号可以看做是传递信号的工具,当数据变化时,信号就会发送改变的信息,以通知信号的订阅者执行方法。

热/冷信号

默认一个信号都是冷信号,也就是值改变了,也不会触发,只有订阅了这个信号,这个信号才会变为热信号,值改变了才会触发。

自己动手写一个RACSignal

//创建信号
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"signal"];
    [subscriber sendCompleted];
    return nil;
}];

//订阅信号
[signal subscribeNext:^(id x) {
    NSLog(@"x = %@", x);
} error:^(NSError *error) {
    NSLog(@"error = %@", error);
} completed:^{
    NSLog(@"completed");
}];

这是一个信号从创建到接收的完整过程,我们接下来看看控制台输出了什么。

2016-01-05 14:33:06.106 RACStudyTest[895:38671] x = signal
2016-01-05 14:33:06.106 RACStudyTest[895:38671] completed

可以看到,创建信号时我们sent了一个signal,在我们订阅subscribeNext时存储在x中的就是这个字符串signal。从这里看出来,不但我们可以给订阅者传递字符串,只要是一个类一个对象我们都可以传递。

另一方面控制台输出了completed说明订阅信号部分的completed块下得方法也被执行了,这是因为饿哦在创建时发送完signal后又发送了一个completed。同理,error下得方法我们也可以这样调用。

融汇贯通一下,我们之前学的所有RAC的用法就是一个创建信号订阅信号的过程!

信号的处理

map

这里的map不是地图,而是映射的意思,就是创建一个订阅者的映射并且返回数据,具体用法我们来看代码。

[[self.textFild.rac_textSignal map:^id(id value) {
    NSLog(@"%@", value);
    return @1;
}] subscribeNext:^(id x) {
    NSLog(@"%@", x);
}];

在TextFild控件中输入good,输出:

2016-01-05 15:02:15.180 RACStudyTest[1418:57245] 1
2016-01-05 15:02:21.710 RACStudyTest[1418:57245] g
2016-01-05 15:02:21.711 RACStudyTest[1418:57245] 1
2016-01-05 15:02:22.954 RACStudyTest[1418:57245] go
2016-01-05 15:02:22.955 RACStudyTest[1418:57245] 1
2016-01-05 15:02:23.464 RACStudyTest[1418:57245] goo
2016-01-05 15:02:23.464 RACStudyTest[1418:57245] 1
2016-01-05 15:02:23.705 RACStudyTest[1418:57245] good
2016-01-05 15:02:23.705 RACStudyTest[1418:57245] 1  

还是之前那个监听textfild编辑变化的例子,可以看到,当信号被订阅变成热信号后,这里的map构造的映射块value的值就是控件中的字符变化,而订阅者x的值就是映射者的返回值1。

根据这个功能我们就可以对我们监测的东西和我们需求的东西进行转换。比如监听了字符串变化,我们需要的时变化后的字符串长度而不是变化的字符串本身,则可以在map的返回值中返回text.length,就可以实时捕获到字符串长度;甚至做一个映射表,将各个变化进行一对一或者一对多或者多对一的处理。

filter

filter就是过滤,它可以帮助你筛选出你需要的信号变化。

[[self.textFild.rac_textSignal filter:^BOOL(NSString *value) {
    return [value length] > 3;
}] subscribeNext:^(id x) {
    NSLog(@"x = %@", x);
}];

输入goodnight,输出:

2016-01-05 15:18:20.492 RACStudyTest[1490:66721] x = good
2016-01-05 15:18:27.917 RACStudyTest[1490:66721] x = goodn
2016-01-05 15:18:28.129 RACStudyTest[1490:66721] x = goodni
2016-01-05 15:18:28.433 RACStudyTest[1490:66721] x = goodnig
2016-01-05 15:18:28.930 RACStudyTest[1490:66721] x = goodnigh
2016-01-05 15:18:29.155 RACStudyTest[1490:66721] x = goodnight

上述例子是在字符串长度大于3时才会输出变换后的字符串。

take/skip/repeat

take是获取,skip是跳过,这两个方法后面跟着的都是NSInteger。所以take 2就是获取前两个信号,skip 2就是跳过前两个。repeat是重复发送信号。

RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"1"];
    [subscriber sendNext:@"2"];
    [subscriber sendNext:@"3"];
    [subscriber sendNext:@"4"];
    [subscriber sendNext:@"5"];
    [subscriber sendCompleted];
    return nil;
}] take:2];

[signal subscribeNext:^(id x) {
    NSLog(@"%@", x);
}completed:^{
    NSLog(@"completed");
}];

这个demo只会输出前两个信号1和2还有完成信号completed,skip,repeat同理。

相似的还有takeLast takeUntil takeWhileBlock skipWhileBlock skipUntilBlock repeatWhileBlock都可以根据字面意思来理解。

delay

延时信号,顾名思义,即延迟发送信号。

RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"delay"];
    [subscriber sendCompleted];
    return nil;
}] delay:2];

NSLog(@"tag");

[signal subscribeNext:^(id x) {
    NSLog(@"%@", x);
}];

输出:

2016-01-05 15:27:55.908 RACStudyTest[1543:71256] tag
2016-01-05 15:27:58.102 RACStudyTest[1543:71256] delay

看时间可以发现订阅者延迟了2秒才收到信号打印出x的值。PS:还有0.2s误差是因为运行到不同代码行的时间差

throttle

节流,在我们做搜索框的时候,有时候需求的时实时搜索,即用户每每输入字符,view都要求展现搜索结果。这时如果用户搜索的字符串较长,那么由于网络请求的延时可能造成UI显示错误,并且多次不必要的请求还会加大服务器的压力,这显然是不合理的,此时我们就需要用到节流。

[[[self.textFild rac_textSignal] throttle:0.5] subscribeNext:^(id x) {
    NSLog(@"%@", x);
}];

加了节流管道,后面跟上了类型为NSTimeInterval的参数后,只有0.5S内信号不产生变化才会发送请求,这样快速的输入也不会造成多次输出。

distinctUntilChanged

网络请求中为了减轻服务器压力,无用的请求我们应该尽可能不发送。distinctUntilChanged的作用是使RAC不会连续发送两次相同的信号,这样就解决了这个问题。

[[[self.textFild rac_textSignal] distinctUntilChanged] subscribeNext:^(id x) {
    NSLog(@"%@", x);
}];   

timeout

超时信号,当超出限定时间后会给订阅者发送error信号。

RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [[RACScheduler mainThreadScheduler] afterDelay:3 schedule:^{
        [subscriber sendNext:@"delay"];
        [subscriber sendCompleted];
    }];
    return nil;
}] timeout:2 onScheduler:[RACScheduler mainThreadScheduler]];

[signal subscribeNext:^(id x) {
    NSLog(@"%@", x);
} error:^(NSError *error) {
    NSLog(@"%@", error);
}];

输出:

2016-01-05 15:41:58.686 RACStudyTest[1604:76716] Error Domain=RACSignalErrorDomain Code=1 "The operation couldn’t be completed. (RACSignalErrorDomain error 1.)"

由于在创建信号是限定了延迟3秒发送,但是加了timeout2秒的限定,所以这一定是一个超时信号。这个信号被订阅后,由于超时,不会执行订阅成功的输出x方法,而是跳到error的块输出了错误信息。timeout在用RAC封装网络请求时可以节省不少的代码量。

ignore

忽略信号,指定一个任意类型的量(可以是字符串,数组等),当需要发送信号时讲进行判断,若相同则该信号会被忽略发送。

[[[self.textFild rac_textSignal] ignore:@"good"] subscribeNext:^(id x) {
    NSLog(@"%@", x);
}];

总结

这篇文章比上一篇稍稍深入了RAC的附加功能,但还有很多例如扁平映射flattenMap,Signal of Signals都没有提到,RAC的东西实在太多太多,我说的也仅仅是九牛一毛。我将继续研究RAC的深入用法,并发文给大家介绍。如果文中有什么错误,也希望大家给我指出来,我将万分感谢。