函数响应式编程(FRP)从入门到"放弃"——图解RACSignal篇

目录

  • 1.RACSignal的创建
  • 2.RACSignal的订阅
  • 3.RACSignal各类操作

一.RACSignal的创建

1.创建单元信号


    NSError *errorObject = [NSError errorWithDomain:@"Something wrong" code:500 userInfo:nil];  

    //基本的4种创建方法
    RACSignal *signal1 = [RACSignal return:@"Some Value"];
    RACSignal *signal2 = [RACSignal error:errorObject];
    RACSignal *signal3 = [RACSignal empty];
    RACSignal *signal4 = [RACSignal never];

2.创建动态信号

    RACSignal *signal5 = [RACSignal createSignal:
                          ^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@1];
        [subscriber sendNext:@2];
        [subscriber sendError:errorObject];
        [subscriber sendCompleted];
        return [RACDisposable disposableWithBlock:^{
        }];
    }];

3.通过Cocoa桥接方式获得一个信号


    RACSignal *signal6 = [view rac_signalForSelector:@selector(setFrame:)];
    RACSignal *signal7 = [view rac_signalForControlEvents:UIControlEventTouchUpInside];
    RACSignal *signal8 = [view rac_willDeallocSignal];

    //KVO的原理实现
    RACSignal *signal9 = RACObserve(view, backgroundColor);

4.通过信号变换获得

RACSignal *signal10 = [signal1 map:^id(NSString *value) {
        return [value substringFromIndex:1];
    }];

5.通过序列转换获得


RACSequence *sequence = @[@"A", @"B", @"C"].rac_sequence;

//这里转换之后获得的signal11是一个很快把信号值吐出的一个信号
RACSignal *signal11 = sequence.signal;

二.RACSignal的订阅

1.订阅方法


    [signal11 subscribeNext:^(id x) {
        NSLog(@"next value is %@", x);
    } error:^(NSError *error) {
        NSLog(@"Ops! Get some error: %@", error);
    } completed:^{
        NSLog(@"It finished success");
    }];

2.绑定方法

//这里相当于KVO,signal每次产生一个color,都是对backgroundColor进行赋值
RAC(view, backgroundColor) = signal10;

3.Cocoa桥接


[view rac_liftSelector:@selector(convertPoint:toView:)
               withSignals:signal1, signal2, nil];

[view rac_liftSelector:@selector(convertRect:toView:)
      withSignalsFromArray:@[signal3, signal4]];

[view rac_liftSelector:@selector(convertRect:toLayer:)
     withSignalOfArguments:signal5];

值得说明的是:如果selector有返回值,那么调用rac_liftSelector会得到一个新的信号。新的信号是调用selector之后的返回值。

4.订阅过程的执行过程


    RACSignal *signal = [RACSignal createSignal:
                         ^RACDisposable *(id<RACSubscriber> subscriber)
    {
1        [subscriber sendNext:@1];
2        [subscriber sendNext:@2];
3        [subscriber sendCompleted];
4        return [RACDisposable disposableWithBlock:^{
5            NSLog(@"dispose");
        }];
    }];
    
    RACDisposable *disposable = [signal subscribeNext:^(id x) {
6        NSLog(@"next value is %@", x);
    } error:^(NSError *error) {
7        NSLog(@"Ops! Get some error: %@", error);
    } completed:^{
8        NSLog(@"It finished success");
    }];
    
9    [disposable dispose];

如果一个signal信号被订阅,那么就会先进入到信号创建的block中,当运行到sendNext,sendCompleted,sendError,接下来就会走到订阅者的对应的subscribeNext,completed,error对应的block块中。主要顺序还是在signal的创建的block中进行执行,只是到了特性的sendNext,sendCompleted,sendError,才会跳到订阅者的对应的block中去执行。订阅者的相应的block执行完了之后,再次回到signal信号的block中继续往下执行。最后signal信号执行完之后会执行disposable。当signal信号发现了disposable,会立即调用创建signal信号里面RACDisposable的block块。

执行过程:1-6-2-6-3-8-4-5-9

RACStream 和 RACSignal的区别 在于 返回数据还是返回事件。
事件总共3种 :值,错误,结束。

值可能是OC对象,RACTuple,甚至是一个RACSignal。

先来说一下RACTuple,RACTuple也是RAC定义的一种数据类型,可以说是NSArray的简化版


    RACTuple *tuple = RACTuplePack(@1, @"haha");
    
    id first = tuple.first;
    id second = tuple.second;
    id last = tuple.last;
    id index1 = tuple[1];
    
    RACTupleUnpack(NSNumber *num, NSString *str) = tuple;

这里有两个宏,RACTuplePack是可以把后面2个对象打包成一个RACTuple。RACTuple也实现了下标访问。使用的过程中并不是id类型,所以需要强转一下类型。类似于这种用法,RACTupleUnpack(NSNumber *num, NSString *str)。

三.RACSignal的各类操作

先来约定好图例

1.单个信号的变换

  • 1.对值的操作
  • 2.对数量的操作
  • 3.对维度的操作
  • 4.对时间间隔的操作

1.对值的操作

  • Map操作
  • MapReplace操作
  • ReduceEach操作
  • not操作
  • and操作
  • or操作
  • reduceApply操作
  • materialize操作
  • dematerialize操作

(1)Map操作


RACSignal *signalA = @[@1, @2, @3, @4].rac_sequence.signal;
    
RACSignal *SignalB = [signalA map:^id(NSNumber *value) {
        return @(value.integerValue * 2);
    }];

如果Map过程中遇到了Error,Error并不会进行Map,而是直接Error返回

(2)MapReplace操作


RACSignal *signalA = @[@1, @2, @3, @4].rac_sequence.signal;
    
RACSignal *signalB = [signalA map:^id(id value) {
        return @8;
    }]; // signalB is --8--8--8--8--|
    
RACSignal *signalC = [signalA mapReplace:@8];
    // signalC is --8--8--8--8--| too.

(3)ReduceEach操作


RACTuple *a = RACTuplePack(@1, @2);
RACTuple *b = RACTuplePack(@2, @3);
RACTuple *c = RACTuplePack(@3, @5);
    
RACSignal *signalA = @[a, b, c].rac_sequence.signal;
    
RACSignal *signalB = [signalA reduceEach:^id(NSNumber *first,
                                                 NSNumber *second) {
        return @(first.integerValue + second.integerValue);
    }];

reduceEach后面可以跟一些具体类型的参数。

(4)not操作


 RACSignal *signalA = @[@0, @1, @1, @0].rac_sequence.signal;
    
 RACSignal *signalB = [signalA not];

(5)and操作


    RACTuple *a = RACTuplePack(@0, @1);
    RACTuple *b = RACTuplePack(@0, @0);
    RACTuple *c = RACTuplePack(@1, @1);
    RACTuple *d = RACTuplePack(@1, @0);
    
    RACSignal *signalA = @[a, b, c, d].rac_sequence.signal;
    
    RACSignal *signalB = [signalA and];

(6)or操作



    RACTuple *a = RACTuplePack(@0, @1);
    RACTuple *b = RACTuplePack(@0, @0);
    RACTuple *c = RACTuplePack(@1, @1);
    RACTuple *d = RACTuplePack(@1, @0);

    RACSignal *signalA = @[a, b, c, d].rac_sequence.signal;
    
    RACSignal *signalB = [signalA or];

(7)reduceApply操作
这里把一个block当做RACTuple的first,然后second,third是其他的参数,当执行reduceApply操作的时候,就会取出第一个参数的block,然后把第二个和第三个参数代入这个block中。

(8)materialize操作
这个操作会把普通的Complete,Error,Next事件,都变成普通的值事件传递出来。

(9)dematerialize操作
RACEvent有一些值

RACEventTypeCompleted,
RACEventTypeError,
RACEventTypeNext

使用dematerialize操作之后,可以把上面的值重新转换成对应的事件。

(10)Scan操作


    RACSignal *signalA = @[@1, @2, @3, @4].rac_sequence.signal;
    
    RACSignal *signalB = [signalA scanWithStart:@0
                                         reduce:^id(NSNumber *running,
                                                    NSNumber *next) {
        return @(running.integerValue + next.integerValue);
    }];

对比下面对数量操作里面的Aggregate操作,这里的优点很明显,每次“扫描”都可以拿到一个值。

- (RACSignal *)scanWithStart:(id)startingValue   
             reduceWithIndex:(id (^)(id running, id next, NSUInteger index))reduceBlock;

上面这个是scan的变种函数。

2.对数量的操作

  • Filter操作
  • Ignore / IgnoreValues操作
  • DistinctUntilChanged操作
  • Take操作
  • Skip操作
  • StartWith操作
  • Repeat操作
  • Retry操作
  • Collect操作
  • Aggregate操作

(1)Filter操作


    RACSignal *signalA = @[@"ab", @"hello", @"ppp", @"0"].rac_sequence.signal;
    
    RACSignal *signalB = [signalA filter:^BOOL(NSString *value) {
        return value.length > 2;
    }];

(2)Ignore / IgnoreValues操作

Ignore操作


    RACSignal *signalA = @[@1, @2, @1, @3].rac_sequence.signal;
    
    RACSignal *signalB = [signalA filter:^BOOL(id value) {
        return ![@1 isEqual:value];
    }];
    
    RACSignal *signalC = [signalA ignore:@1];

IgnoreValues操作

进行这个操作之后,新信号就没有值了,只剩下Error,Complete事件了。


    RACSignal *signalA = @[@1, @2, @1, @3].rac_sequence.signal;
    
    RACSignal *signalB = [signalA ignoreValues];

(3)DistinctUntilChanged操作
这个操作其实就是去重操作。


    RACSignal *signalA = @[@1,@1,@2,@2,@3].rac_sequence.signal;
    
    RACSignal *signalB = [signalA distinctUntilChanged];

(4)Take操作


    RACSignal *signalA = @[@1, @2, @3].rac_sequence.signal;
    
    RACSignal *signalB = [signalA take:2];

(5)Skip操作


    RACSignal *signalA = @[@1, @2, @3].rac_sequence.signal;
    
    RACSignal *signalB = [signalA skip:2];

take和Skip变种方法


- (RACSignal *)takeLast:(NSUInteger)count;
- (RACSignal *)takeUntilBlock:(BOOL (^)(id x))predicate;
- (RACSignal *)takeWhileBlock:(BOOL (^)(id x))predicate;
- (RACSignal *)skipUntilBlock:(BOOL (^)(id x))predicate;
- (RACSignal *)skipWhileBlock:(BOOL (^)(id x))predicate;

predicate是传入一个要不要或者跳不跳过的规则。

(6)StartWith操作


RACSignal *signalA = @[@"ab", @"hello", @"app", @"1"].rac_sequence.signal;
    
RACSignal *signalB = [signalA startWith:@"Start"];

(7)Repeat操作


RACSignal *signalA = @[@"ab", @"hello"].rac_sequence.signal;
RACSignal *signalB = [signalA repeat];

(8)Retry操作

在一些网络请求中,如果我们把网络获取封装成一个信号,网络请求失败的时候,这时候就会抛出一个错误,网络不能连接。这个时候客户端可能会有重新连接的需求。

RACSignal *signalA = @[@"ab"].rac_sequence.signal;
RACSignal *signalB = [signalA retry:2];

Retry后面是重试的次数

这里会引出副作用操作


    RACSignal *signalA = @[@"ab", @"hello", @"ppp", @"0"].rac_sequence.signal;
    
    RACSignal *signalB = [signalA map:^id(id value) {
        // do some thing;
        return value;
    }];
    
    RACSignal *signalC = [signalA doNext:^(id x) {
        // do some thing;
    }];

副作用操作就是指的是不影响值,额外做的一些操作,比如说做一些动画,屏幕是打出一些字,弹框等等。
有以下一些便捷方法

- (RACSignal *)doError:(void (^)(NSError *error))block;
- (RACSignal *)doCompleted:(void (^)(void))block;
- (RACSignal *)initially:(void (^)(void))block;
- (RACSignal *)finally:(void (^)(void))block;

(9)Collect操作

RACSignal *signalA = @[@"ab", @"hello", @"ppp", @"0"].rac_sequence.signal;
    
RACSignal *signalB = [signalA collect];

(10)Aggregate操作
这个函数其实是一个“折叠函数”

RACSignal *signalA = @[@1, @2, @3, @4].rac_sequence.signal;
    
RACSignal *signalB = [signalA aggregateWithStart:@0
                                              reduce:^id(NSNumber *running,
                                                         NSNumber *next) {
        return @(running.integerValue + next.integerValue);
    }];

值得注意的是,这个函数在终止的时候才有返回,不终止就一直不返回,如果遇到了Repeat信号,那就一直都不会返回了。aggregateWithStart有一个初始值。缺点是变化中拿不到值,只有最后才有值,对比值操作里面的Scan操作,还是Scan操作比较好。

 - (RACSignal *)aggregateWithStart:(id)start 
                   reduceWithIndex:(id (^)(id running, id next, NSUInteger index))reduceBlock;
 - (RACSignal *)aggregateWithStartFactory:(id (^)(void))startFactory 
                                   reduce:(id (^)(id running, id next))reduceBlock;

这个是aggregate的变种函数。

3.对时间间隔的操作

  • 单位间隔时间信号
  • Delay操作
  • Throttle操作(节流操作)

(1)单位间隔时间信号


+ (RACSignal *)interval:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler;
+ (RACSignal *)interval:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler withLeeway:(NSTimeInterval)leeway;

上面这2个方法,会返回以interval为时间间隔的时间信号。每隔interval就会发出一个信号。

(2)Delay操作

将源信号延迟1秒发送。


RACSignal *signalB = [signalA delay:1];

(3)Throttle操作(节流操作)


RACSignal *signalB = [signalA throttle:2];

throttle后面跟着是间隔多少秒。如果间隔多少秒之后都没有值,就把之前监听到的值发出来。比如上述的例子,1发出来之后,间隔2秒,2秒内又来了2,继续监听2秒,2秒内又来了3,继续监听2秒,2秒内没有其他的值了,这时候就把3发送出来。如此道理,会发出5,6。

这个函数在我们开发中会用到的场景是,搜索操作。
用户拼命在搜索框内输入,这个时候如果每输入一次就网络请求一次,那又浪费流量,又没有必要。这个时候就可以用到这个throttle了。用户输完1秒内没有输入了,这个时候就可以去请求网络了。


- (RACSignal *)throttle:(NSTimeInterval)interval valuesPassingTest:(BOOL (^)(id next))predicate;
- (RACSignal *)bufferWithTime:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler;

这些是Throttle的变种函数。

2.多个信号的组合

  • 1.Concat组合操作
  • 2.Merge组合操作
  • 3.Zip组合操作
  • 4.CombineLatest组合操作
  • 5.Sample组合操作
  • 6.TakeUntil / TakeUntilReplacement组合操作

1.Concat组合操作


    RACSignal *signalA = @[@1, @2, @3, @4, @5].rac_sequence.signal;
    RACSignal *signalB = @[@6, @7].rac_sequence.signal;
    
    RACSignal *signalC = [signalA concat:signalB];

Concat工作原理:先订阅A,当A都结束了之后才会继续订阅B,这个时候可以看到,B早已结束了。这里也体现出了一个信号的定义和一个信号的订阅,是分离的。

如果A发生了错误,那就没有B什么事了。

如果B发生了一个错误,那么C就会传递这个错误。

2.Merge组合操作


    RACSignal *signalA = @[@1, @2, @3, @4, @5].rac_sequence.signal;
    RACSignal *signalB = @[@6, @7].rac_sequence.signal;
    
    RACSignal *signalC = [signalA merge:signalB];
    RACSignal *signalC = [RACSignal merge:@[signalA, signalB]];
    RACSignal *signalC = [RACSignal merge:RACTuplePack(signalA, signalB)];
 

如果SignalA,SignalB,是2个线程,那么SignalC会穿梭在2个线程中发送。如上图的例子,1,2,3,5发在线程A,4,6,7发在线程B里。

Merge在实际开发中的使用场景可能会出现如下场景:


RACSignal *appearSignal = [[self rac_signalForSelector:@selector(viewDidAppear:)]
                               mapReplace:@YES];
RACSignal *disappearSignal = [[self rac_signalForSelector:@selector(viewWillDisappear:)]
                                  mapReplace:@NO];
RACSignal *activeSignal = [RACSignal merge:@[appearSignal, disappearSignal]];

activeSignal信号得到的就是app是否在前台的信号。

3.Zip组合操作

RACSignal *signalA = @[@1, @2, @3, @5].rac_sequence.signal;
RACSignal *signalB = @[@4, @6, @7].rac_sequence.signal;

RACSignal *signalC = [signalA zipWith:signalB];
RACSignal *signalC = [RACSignal zip:@[signalA, signalB]];
RACSignal *signalC = [RACSignal zip:RACTuplePack(signalA, signalB)];

注意图中的RACTuple的颜色。谁来控制终止由谁短来控制。发送的时机是谁更晚,就发送谁。(1,4)4更晚,就在4的时候发送RACTuple,(2,6)6更晚,6的时候发送RACTuple。(3,7)3更晚,3的时候发送RACTuple。

4.CombineLatest组合操作


    RACSignal *signalA = @[@1, @2, @3].rac_sequence.signal;
    RACSignal *signalB = @[@4, @6, @7].rac_sequence.signal;
    
     RACSignal *signalC = [signalA combineLatestWith:signalB];
     RACSignal *signalC = [RACSignal combineLatest:@[signalA, signalB]];
     RACSignal *signalC = [RACSignal combineLatest:RACTuplePack(signalA, signalB)];

永远都结合最新的值作为新值返回。谁来控制终止由谁长来控制。

+ (RACSignal *)combineLatest:(id<NSFastEnumeration>)signals reduce:(id (^)())reduceBlock;
+ (RACSignal *)zip:(id<NSFastEnumeration>)streams reduce:(id (^)())reduceBlock;

这分别是对应的combineLatest和Zip的语法糖似的便捷操作。都只不过是在后面加了一个Reduce。

5.Sample组合操作

RACSignal *signalC = [signalA sample:signalB];

谁早结束,那么结果信号就跟着谁一起结束。

如果遇到了错误,这个函数相当于快门。一样会把Error取得,并返回。

6.TakeUntil / TakeUntilReplacement组合操作


RACSignal *signalC = [signalA takeUntil:signalB];

当出现B的时候,就终止A。


RACSignal *signalC = [signalA takeUntilReplacement:signalB];

当出现B的时候,就终止A,后面再紧接着B。