Discover more content...

Discover more content...

Enter some keywords in the search box above, we will do our best to offer you relevant results.

Results

We're sorry!

Sorry about that!

We couldn't find any results for your search. Please try again with another keywords.

函数响应式编程(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。