ReactiveCocoa 中 RACSignal 所有變換操作底層實現分析(上)

一縷殤流化隱半邊冰霜發表於2019-03-03

前言

上篇文章中,詳細分析了RACSignal是建立和訂閱的詳細過程。看到底層原始碼實現後,就能發現,ReactiveCocoa這個FRP的庫,實現響應式(RP)是用Block閉包來實現的,而並不是用KVC / KVO實現的。

在ReactiveCocoa整個庫中,RACSignal佔據著比較重要的位置,而RACSignal的變換操作更是整個RACStream流操作核心之一。在上篇文章中也詳細分析了bind操作的實現。RACsignal很多變換操作都是基於bind操作來實現的。在開始本篇底層實現分析之前,先簡單回顧一下上篇文章中分析的bind函式,這是這篇文章分析的基礎。

bind函式可以簡單的縮寫成下面這樣子。


- (RACSignal *)bind:(RACStreamBindBlock (^)(void))block;
{
    return [RACSignal createSignal:^RACDisposable *(id subscriber) {

        RACStreamBindBlock bindBlock = block();
        [self subscribeNext:^(id x) {    //(1)
            BOOL stop = NO;
            RACSignal *signal = (RACSignal *)bindBlock(x, &stop); //(2)
            if (signal == nil || stop) {
                [subscriber sendCompleted];
            } else {
                [signal subscribeNext:^(id x) {
                    [subscriber sendNext:x];  //(3)
                } error:^(NSError *error) {
                    [subscriber sendError:error];
                } completed:^{

                }];
            }
        } error:^(NSError *error) {
            [subscriber sendError:error];
        } completed:^{
            [subscriber sendCompleted];
        }];
        return nil;
    }];
}複製程式碼

當bind變換之後的訊號被訂閱,就開始執行bind函式中return的block閉包。

  1. 在bind閉包中,先訂閱原先的訊號A。
  2. 在訂閱原訊號A的didSubscribe閉包中進行訊號變換,變換中用到的block閉包是外部傳遞進來的,也就是bind函式的入參。變換結束,得到新的訊號B
  3. 訂閱新的訊號B,拿到bind變化之後的訊號的訂閱者subscriber,對其傳送新的訊號值。

ReactiveCocoa 中 RACSignal 所有變換操作底層實現分析(上)

簡要的過程如上圖,bind函式中進行了2次訂閱的操作,第一次訂閱是為了拿到signalA的值,第二次訂閱是為了把signalB的新值發給bind變換之後得到的signalB的訂閱者。

回顧完bind底層實現之後,就可以開始繼續本篇文章的分析了。

目錄

  • 1.變換操作
  • 2.時間操作

一.變換操作

ReactiveCocoa 中 RACSignal 所有變換操作底層實現分析(上)

我們都知道RACSignal是繼承自RACStream的,而在底層的RACStream上也定義了一些基本的訊號變換的操作,所以這些操作在RACSignal上同樣適用。如果在RACsignal中沒有重寫這些方法,那麼呼叫這些操作,實際是呼叫的父類RACStream的操作。下面分析的時候,會把實際呼叫父類RACStream的操作的地方都標註出來。

1.Map: (在父類RACStream中定義的)

map操作一般是用來訊號變換的。


    RACSignal *signalB = [signalA map:^id(NSNumber *value) {
        return @([value intValue] * 10);
    }];複製程式碼

ReactiveCocoa 中 RACSignal 所有變換操作底層實現分析(上)

來看看底層是如何實現的。


- (instancetype)map:(id (^)(id value))block {
    NSCParameterAssert(block != nil);

    Class class = self.class;

    return [[self flattenMap:^(id value) {
        return [class return:block(value)];
    }] setNameWithFormat:@"[%@] -map:", self.name];
}複製程式碼

這裡實現程式碼比較嚴謹,先判斷self的型別。因為RACStream的子類中會有一些子類會重寫這些方法,所以需要判斷self的型別,在回撥中可以回撥到原型別的方法中去。

由於本篇文章中我們都分析RACSignal的操作,所以這裡的self.class是RACDynamicSignal型別的。相應的在return返回值中也返回class,即RACDynamicSignal型別的訊號。

從map實現程式碼上來看,map實現是用了flattenMap函式來實現的。把map的入參閉包,放到了flattenMap的返回值中。

在來看看flattenMap的實現:


- (instancetype)flattenMap:(RACStream * (^)(id value))block {
    Class class = self.class;

    return [[self bind:^{
        return ^(id value, BOOL *stop) {
            id stream = block(value) ?: [class empty];
            NSCAssert([stream isKindOfClass:RACStream.class], @"Value returned from -flattenMap: is not a stream: %@", stream);

            return stream;
        };
    }] setNameWithFormat:@"[%@] -flattenMap:", self.name];
}複製程式碼

flattenMap算是對bind函式的一種封裝。bind函式的入參是一個RACStreamBindBlock型別的閉包。而flattenMap函式的入參是一個value,返回值RACStream型別的閉包。

在flattenMap中,返回block(value)的訊號,如果訊號為nil,則返回[class empty]。

先來看看為空的情況。當block(value)為空,返回[RACEmptySignal empty],empty就是建立了一個RACEmptySignal型別的訊號:


+ (RACSignal *)empty {
#ifdef DEBUG
    // Create multiple instances of this class in DEBUG so users can set custom
    // names on each.
    return [[[self alloc] init] setNameWithFormat:@"+empty"];
#else
    static id singleton;
    static dispatch_once_t pred;

    dispatch_once(&pred, ^{
        singleton = [[self alloc] init];
    });

    return singleton;
#endif
}複製程式碼

RACEmptySignal型別的訊號又是什麼呢?



- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    NSCParameterAssert(subscriber != nil);

    return [RACScheduler.subscriptionScheduler schedule:^{
        [subscriber sendCompleted];
    }];
}複製程式碼

RACEmptySignal是RACSignal的子類,一旦訂閱它,它就會同步的傳送completed完成訊號給訂閱者。

所以flattenMap返回一個訊號,如果訊號不存在,就返回一個completed完成訊號給訂閱者。

再來看看flattenMap返回的訊號是怎麼變換的。

block(value)會把原訊號傳送過來的value傳遞給flattenMap的入參。flattenMap的入參是一個閉包,閉包的引數也是value的:


^(id value) { return [class return:block(value)]; }複製程式碼

這個閉包返回一個訊號,訊號型別和原訊號的型別一樣,即RACDynamicSignal型別的,值是block(value)。這裡的閉包是外面map傳進來的:


^id(NSNumber *value) { return @([value intValue] * 10); }複製程式碼

在這個閉包中把原訊號的value值傳進去進行變換,變換結束之後,包裝成和原訊號相同型別的訊號,返回。返回的訊號作為bind函式的閉包的返回值。這樣訂閱新的map之後的訊號就會拿到變換之後的值。

2.MapReplace: (在父類RACStream中定義的)

一般用法如下:


RACSignal *signalB = [signalA mapReplace:@"A"];複製程式碼

ReactiveCocoa 中 RACSignal 所有變換操作底層實現分析(上)

效果是不管A訊號傳送什麼值,都替換成@“A”。


- (instancetype)mapReplace:(id)object {
    return [[self map:^(id _) {
        return object;
    }] setNameWithFormat:@"[%@] -mapReplace: %@", self.name, [object rac_description]];
}複製程式碼

看底層原始碼就知道,它並不去關心原訊號傳送的是什麼值,原訊號傳送什麼值,都返回入參object的值。

3.reduceEach: (在父類RACStream中定義的)

reduce是減少,聚合在一起的意思,reduceEach就是每個訊號內部都聚合在一起。


    RACSignal *signalB = [signalA reduceEach:^id(NSNumber *num1 , NSNumber *num2){
        return @([num1 intValue] + [num2 intValue]);
    }];複製程式碼

ReactiveCocoa 中 RACSignal 所有變換操作底層實現分析(上)

reduceEach後面比如傳入一個元組RACTuple型別,否則會報錯。


- (instancetype)reduceEach:(id (^)())reduceBlock {
    NSCParameterAssert(reduceBlock != nil);

    __weak RACStream *stream __attribute__((unused)) = self;
    return [[self map:^(RACTuple *t) {
        NSCAssert([t isKindOfClass:RACTuple.class], @"Value from stream %@ is not a tuple: %@", stream, t);
        return [RACBlockTrampoline invokeBlock:reduceBlock withArguments:t];
    }] setNameWithFormat:@"[%@] -reduceEach:", self.name];
}複製程式碼

這裡有兩個斷言,一個是判斷傳入的reduceBlock閉包是否為空,另一個斷言是判斷閉包的入參是否是RACTuple型別的。


@interface RACBlockTrampoline : NSObject
@property (nonatomic, readonly, copy) id block;
+ (id)invokeBlock:(id)block withArguments:(RACTuple *)arguments;
@end複製程式碼

RACBlockTrampoline就是一個儲存了一個block閉包的物件,它會根據傳進來的引數,動態的構造一個NSInvocation,並執行。

reduceEach把入參reduceBlock作為RACBlockTrampoline的入參invokeBlock傳進去,以及每個RACTuple也傳到RACBlockTrampoline中。


- (id)invokeWithArguments:(RACTuple *)arguments {
    SEL selector = [self selectorForArgumentCount:arguments.count];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:selector]];
    invocation.selector = selector;
    invocation.target = self;

    for (NSUInteger i = 0; i < arguments.count; i++) {
        id arg = arguments[i];
        NSInteger argIndex = (NSInteger)(i + 2);
        [invocation setArgument:&arg atIndex:argIndex];
    }

    [invocation invoke];

    __unsafe_unretained id returnVal;
    [invocation getReturnValue:&returnVal];
    return returnVal;
}複製程式碼

第一步就是先計算入參一個元組RACTuple裡面元素的個數。


- (SEL)selectorForArgumentCount:(NSUInteger)count {
    NSCParameterAssert(count > 0);

    switch (count) {
        case 0: return NULL;
        case 1: return @selector(performWith:);
        case 2: return @selector(performWith::);
        case 3: return @selector(performWith:::);
        case 4: return @selector(performWith::::);
        case 5: return @selector(performWith:::::);
        case 6: return @selector(performWith::::::);
        case 7: return @selector(performWith:::::::);
        case 8: return @selector(performWith::::::::);
        case 9: return @selector(performWith:::::::::);
        case 10: return @selector(performWith::::::::::);
        case 11: return @selector(performWith:::::::::::);
        case 12: return @selector(performWith::::::::::::);
        case 13: return @selector(performWith:::::::::::::);
        case 14: return @selector(performWith::::::::::::::);
        case 15: return @selector(performWith:::::::::::::::);
    }

    NSCAssert(NO, @"The argument count is too damn high! Only blocks of up to 15 arguments are currently supported.");
    return NULL;
}複製程式碼

可以看到最多支援元組內元素的個數是15個。

這裡我們假設以元組裡面有2個元素為例。


- (id)performWith:(id)obj1 :(id)obj2 {
    id (^block)(id, id) = self.block;
    return block(obj1, obj2);
}複製程式碼

對應的Type Encoding如下:

argument return value 0 1 2 3
methodSignature @ @ : @ @

argument0和argument1分別對應著隱藏引數self和_cmd,所以對應著的型別是@和:,從argument2開始,就是入參的Type Encoding了。

所以在構造invocation的引數的時候,argIndex是要偏移2個位置的。即從(i + 2)開始設定引數。

當動態構造了一個invocation方法之後,[invocation invoke]呼叫這個動態方法,也就是執行了外部的reduceBlock閉包,閉包裡面是我們想要訊號變換的規則。

閉包執行結束得到returnVal返回值。這個返回值就是整個RACBlockTrampoline的返回值了。這個返回值也作為了map閉包裡面的返回值。

接下去的操作就完全轉換成了map的操作了。上面已經分析過map操作了,這裡就不贅述了。

4. reduceApply

舉個例子:


    RACSignal *signalA = [RACSignal createSignal:
                         ^RACDisposable *(id<RACSubscriber> subscriber)
                         {
                             id block = ^id(NSNumber *first,NSNumber *second,NSNumber *third) {
                                 return @(first.integerValue + second.integerValue * third.integerValue);
                             };

                             [subscriber sendNext:RACTuplePack(block,@2 , @3 , @8)];
                             [subscriber sendNext:RACTuplePack((id)(^id(NSNumber *x){return @(x.intValue * 10);}),@9,@10,@30)];

                             [subscriber sendCompleted];
                             return [RACDisposable disposableWithBlock:^{
                                 NSLog(@"signal dispose");
                             }];
                         }];

    RACSignal *signalB = [signalA reduceApply];複製程式碼

使用reduceApply的條件也是需要訊號裡面的值是元組RACTuple。不過這裡和reduceEach不同的是,原訊號中每個元祖RACTuple的第0位必須要為一個閉包,後面n位為這個閉包的入參,第0位的閉包有幾個引數,後面就需要跟幾個引數。

如上述例子中,第一個元組第0位的閉包有3個引數,所以第一個元組後面還要跟3個引數。第二個元組的第0位的閉包只有一個引數,所以後面只需要跟一個引數。

當然後面可以跟更多的引數,如第二個元組,閉包後面跟了3個引數,但是隻有第一個引數是有效值,後面那2個引數是無效不起作用的。唯一需要注意的就是後面跟的引數個數一定不能少於第0位閉包入參的個數,否則就會報錯。

上面例子輸出


26  // 26 = 2 + 3 * 890  // 90 = 9 * 10複製程式碼

看看底層實現:



- (RACSignal *)reduceApply {
    return [[self map:^(RACTuple *tuple) {
        NSCAssert([tuple isKindOfClass:RACTuple.class], @"-reduceApply must only be used on a signal of RACTuples. Instead, received: %@", tuple);
        NSCAssert(tuple.count > 1, @"-reduceApply must only be used on a signal of RACTuples, with at least a block in tuple[0] and its first argument in tuple[1]");

        // We can`t use -array, because we need to preserve RACTupleNil
        NSMutableArray *tupleArray = [NSMutableArray arrayWithCapacity:tuple.count];
        for (id val in tuple) {
            [tupleArray addObject:val];
        }
        RACTuple *arguments = [RACTuple tupleWithObjectsFromArray:[tupleArray subarrayWithRange:NSMakeRange(1, tupleArray.count - 1)]];

        return [RACBlockTrampoline invokeBlock:tuple[0] withArguments:arguments];
    }] setNameWithFormat:@"[%@] -reduceApply", self.name];
}複製程式碼

這裡也有2個斷言,第一個是確保傳入的引數是RACTuple型別,第二個斷言是確保元組RACTuple裡面的元素各種至少是2個。因為下面取引數是直接從1號位開始取的。

reduceApply做的事情和reduceEach基本是等效的,只不過變換規則的block閉包一個是外部傳進去的,一個是直接打包在每個訊號元組RACTuple中第0位中。

5. materialize

這個方法會把訊號包裝成RACEvent型別。



- (RACSignal *)materialize {
    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        return [self subscribeNext:^(id x) {
            [subscriber sendNext:[RACEvent eventWithValue:x]];
        } error:^(NSError *error) {
            [subscriber sendNext:[RACEvent eventWithError:error]];
            [subscriber sendCompleted];
        } completed:^{
            [subscriber sendNext:RACEvent.completedEvent];
            [subscriber sendCompleted];
        }];
    }] setNameWithFormat:@"[%@] -materialize", self.name];
}複製程式碼

sendNext會包裝成[RACEvent eventWithValue:x],error會包裝成[RACEvent eventWithError:error],completed會被包裝成RACEvent.completedEvent。注意,當原訊號error和completed,新訊號都會傳送sendCompleted。

6. dematerialize

這個操作是materialize的逆向操作。它會把包裝成RACEvent訊號重新還原為正常的值訊號。


- (RACSignal *)dematerialize {
    return [[self bind:^{
        return ^(RACEvent *event, BOOL *stop) {
            switch (event.eventType) {
                case RACEventTypeCompleted:
                    *stop = YES;
                    return [RACSignal empty];

                case RACEventTypeError:
                    *stop = YES;
                    return [RACSignal error:event.error];

                case RACEventTypeNext:
                    return [RACSignal return:event.value];
            }
        };
    }] setNameWithFormat:@"[%@] -dematerialize", self.name];
}複製程式碼

這裡的實現也用到了bind函式,它會把原訊號進行一個變換。新的訊號會根據event.eventType進行轉換。RACEventTypeCompleted被轉換成[RACSignal empty],RACEventTypeError被轉換成[RACSignal error:event.error],RACEventTypeNext被轉換成[RACSignal return:event.value]。

7. not


- (RACSignal *)not {
    return [[self map:^(NSNumber *value) {
        NSCAssert([value isKindOfClass:NSNumber.class], @"-not must only be used on a signal of NSNumbers. Instead, got: %@", value);

        return @(!value.boolValue);
    }] setNameWithFormat:@"[%@] -not", self.name];
}複製程式碼

not操作需要傳入的值都是NSNumber型別的。不是NSNumber型別會報錯。not操作會把每個NSNumber按照BOOL的規則,取非,當成新訊號的值。

8. and


- (RACSignal *)and {
    return [[self map:^(RACTuple *tuple) {
        NSCAssert([tuple isKindOfClass:RACTuple.class], @"-and must only be used on a signal of RACTuples of NSNumbers. Instead, received: %@", tuple);
        NSCAssert(tuple.count > 0, @"-and must only be used on a signal of RACTuples of NSNumbers, with at least 1 value in the tuple");

        return @([tuple.rac_sequence all:^(NSNumber *number) {
            NSCAssert([number isKindOfClass:NSNumber.class], @"-and must only be used on a signal of RACTuples of NSNumbers. Instead, tuple contains a non-NSNumber value: %@", tuple);

            return number.boolValue;
        }]);
    }] setNameWithFormat:@"[%@] -and", self.name];
}複製程式碼

and操作需要原訊號的每個訊號都是元組RACTuple型別的,因為只有這樣,RACTuple型別裡面的每個元素的值才能進行&運算。

and操作裡面有3處斷言。第一處,判斷入參是不是元組RACTuple型別的。第二處,判斷RACTuple型別裡面至少包含一個NSNumber。第三處,判斷RACTuple裡面是否都是NSNumber型別,有一個不符合,都會報錯。


- (RACSequence *)rac_sequence {
    return [RACTupleSequence sequenceWithTupleBackingArray:self.backingArray offset:0];
}複製程式碼

RACTuple型別先轉換成RACTupleSequence。


+ (instancetype)sequenceWithTupleBackingArray:(NSArray *)backingArray offset:(NSUInteger)offset {
    NSCParameterAssert(offset <= backingArray.count);

    if (offset == backingArray.count) return self.empty;

    RACTupleSequence *seq = [[self alloc] init];
    seq->_tupleBackingArray = backingArray;
    seq->_offset = offset;
    return seq;
}複製程式碼

backingArray是一個陣列NSArry。這裡關於RACTupleSequence和RACTuple會在以後的文章中詳細分析,本篇以分析RACSignal為主。

RACTuple型別先轉換成RACTupleSequence,即存成了一個陣列。


- (BOOL)all:(BOOL (^)(id))block {
    NSCParameterAssert(block != NULL);

    NSNumber *result = [self foldLeftWithStart:@YES reduce:^(NSNumber *accumulator, id value) {
        return @(accumulator.boolValue && block(value));
    }];

    return result.boolValue;
}

- (id)foldLeftWithStart:(id)start reduce:(id (^)(id, id))reduce {
    NSCParameterAssert(reduce != NULL);

    if (self.head == nil) return start;

    for (id value in self) {
        start = reduce(start, value);
    }

    return start;
}複製程式碼

for會遍歷RACSequence裡面存的每一個值,分別都去呼叫reduce( )閉包。start的初始值為YES。reduce( )閉包是:


^(NSNumber *accumulator, id value) { return @(accumulator.boolValue && block(value)); }複製程式碼

這裡又會去呼叫block( )閉包:


^(NSNumber *number) { return number.boolValue; }複製程式碼

number是原訊號RACTuple的第一個值。第一次迴圈reduce( )閉包是拿YES和原訊號RACTuple的第一個值進行&計算。第二個迴圈reduce( )閉包是拿原訊號RACTuple的第一個值和第二個值進行&計算,得到的值參與下一次迴圈,與第三個值進行&計算,如此下去。這也是摺疊函式的意思,foldLeft從左邊開始摺疊。fold函式會從左至右,把RACTuple轉換成的陣列裡面每個值都一個接著一個進行&計算。

每個RACTuple都map成這樣的一個BOOL值。接下去訊號就map成了一個新的訊號。

9. or



- (RACSignal *)or {
    return [[self map:^(RACTuple *tuple) {
        NSCAssert([tuple isKindOfClass:RACTuple.class], @"-or must only be used on a signal of RACTuples of NSNumbers. Instead, received: %@", tuple);
        NSCAssert(tuple.count > 0, @"-or must only be used on a signal of RACTuples of NSNumbers, with at least 1 value in the tuple");

        return @([tuple.rac_sequence any:^(NSNumber *number) {
            NSCAssert([number isKindOfClass:NSNumber.class], @"-or must only be used on a signal of RACTuples of NSNumbers. Instead, tuple contains a non-NSNumber value: %@", tuple);

            return number.boolValue;
        }]);
    }] setNameWithFormat:@"[%@] -or", self.name];
}複製程式碼

or操作的實現和and操作的實現大體類似。3處斷言的作用和and操作完全一致,這裡就不再贅述了。or操作的重點在any函式的實現上。or操作的入參也必須是RACTuple型別的。



- (BOOL)any:(BOOL (^)(id))block {
    NSCParameterAssert(block != NULL);

    return [self objectPassingTest:block] != nil;
}


- (id)objectPassingTest:(BOOL (^)(id))block {
    NSCParameterAssert(block != NULL);

    return [self filter:block].head;
}


- (instancetype)filter:(BOOL (^)(id value))block {
    NSCParameterAssert(block != nil);

    Class class = self.class;

    return [[self flattenMap:^ id (id value) {
        if (block(value)) {
            return [class return:value];
        } else {
            return class.empty;
        }
    }] setNameWithFormat:@"[%@] -filter:", self.name];
}複製程式碼

any會依次判斷RACTupleSequence陣列裡面的值,依次每個進行filter。如果value對應的BOOL值是YES,就轉換成一個RACTupleSequence訊號。如果對應的是NO,則轉換成一個empty訊號。

只要RACTuple為NO,就一直返回empty訊號,直到BOOL值為YES,就返回1。map變換訊號後變成成1。找到了YES之後的值就不會再判斷了。如果沒有找到YES,中間都是NO的話,一直遍歷到陣列最後一個,訊號只能返回0。

10. any:



- (RACSignal *)any:(BOOL (^)(id object))predicateBlock {
    NSCParameterAssert(predicateBlock != NULL);

    return [[[self materialize] bind:^{
        return ^(RACEvent *event, BOOL *stop) {
            if (event.finished) {
                *stop = YES;
                return [RACSignal return:@NO];
            }

            if (predicateBlock(event.value)) {
                *stop = YES;
                return [RACSignal return:@YES];
            }

            return [RACSignal empty];
        };
    }] setNameWithFormat:@"[%@] -any:", self.name];
}複製程式碼

原訊號會先經過materialize轉換包裝成RACEvent事件。依次判斷predicateBlock(event.value)值的BOOL值,如果返回YES,就包裝成RACSignal的新訊號,傳送YES出去,並且stop接下來的訊號。如果返回MO,就返回[RACSignal empty]空訊號。直到event.finished,返回[RACSignal return:@NO]。

所以any:操作的目的是找到第一個滿足predicateBlock條件的值。找到了就返回YES的RACSignal的訊號,如果沒有找到,返回NO的RACSignal。

11. any


- (RACSignal *)any {
    return [[self any:^(id x) {
        return YES;
    }] setNameWithFormat:@"[%@] -any", self.name];
}複製程式碼

any操作是any:操作中的一種情況。即predicateBlock閉包永遠都返回YES,所以any操作之後永遠都只能得到一個只傳送一個YES的新訊號。

12. all:



- (RACSignal *)all:(BOOL (^)(id object))predicateBlock {
    NSCParameterAssert(predicateBlock != NULL);

    return [[[self materialize] bind:^{
        return ^(RACEvent *event, BOOL *stop) {
            if (event.eventType == RACEventTypeCompleted) {
                *stop = YES;
                return [RACSignal return:@YES];
            }

            if (event.eventType == RACEventTypeError || !predicateBlock(event.value)) {
                *stop = YES;
                return [RACSignal return:@NO];
            }

            return [RACSignal empty];
        };
    }] setNameWithFormat:@"[%@] -all:", self.name];
}複製程式碼

all:操作和any:有點類似。原訊號會先經過materialize轉換包裝成RACEvent事件。對原訊號傳送的每個訊號都依次判斷predicateBlock(event.value)是否是NO 或者event.eventType == RACEventTypeError。如果predicateBlock(event.value)返回NO或者出現了錯誤,新的訊號都返回NO。如果一直都沒出現問題,在RACEventTypeCompleted的時候傳送YES。

all:可以用來判斷整個原訊號傳送過程中是否有錯誤事件RACEventTypeError,或者是否存在predicateBlock為NO的情況。可以把predicateBlock設定成一個正確條件。如果原訊號出現錯誤事件,或者不滿足設定的錯誤條件,都會傳送新訊號返回NO。如果全過程都沒有出錯,或者都滿足predicateBlock設定的條件,則一直到RACEventTypeCompleted,傳送YES的新訊號。

13. repeat


- (RACSignal *)repeat {
    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        return subscribeForever(self,
                                ^(id x) {
                                    [subscriber sendNext:x];
                                },
                                ^(NSError *error, RACDisposable *disposable) {
                                    [disposable dispose];
                                    [subscriber sendError:error];
                                },
                                ^(RACDisposable *disposable) {
                                    // Resubscribe.
                                });
    }] setNameWithFormat:@"[%@] -repeat", self.name];
}複製程式碼

repeat操作返回一個subscribeForever閉包,閉包裡面要傳入4個引數。


static RACDisposable *subscribeForever (RACSignal *signal, void (^next)(id), void (^error)(NSError *, RACDisposable *), void (^completed)(RACDisposable *)) {
    next = [next copy];
    error = [error copy];
    completed = [completed copy];

    RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable];

    RACSchedulerRecursiveBlock recursiveBlock = ^(void (^recurse)(void)) {
        RACCompoundDisposable *selfDisposable = [RACCompoundDisposable compoundDisposable];
        [compoundDisposable addDisposable:selfDisposable];

        __weak RACDisposable *weakSelfDisposable = selfDisposable;

        RACDisposable *subscriptionDisposable = [signal subscribeNext:next error:^(NSError *e) {
            @autoreleasepool {
                error(e, compoundDisposable);
                [compoundDisposable removeDisposable:weakSelfDisposable];
            }

            recurse();
        } completed:^{
            @autoreleasepool {
                completed(compoundDisposable);
                [compoundDisposable removeDisposable:weakSelfDisposable];
            }

            recurse();
        }];

        [selfDisposable addDisposable:subscriptionDisposable];
    };

    // Subscribe once immediately, and then use recursive scheduling for any
    // further resubscriptions.
    recursiveBlock(^{
        RACScheduler *recursiveScheduler = RACScheduler.currentScheduler ?: [RACScheduler scheduler];

        RACDisposable *schedulingDisposable = [recursiveScheduler scheduleRecursiveBlock:recursiveBlock];
        [compoundDisposable addDisposable:schedulingDisposable];
    });

    return compoundDisposable;
}複製程式碼

subscribeForever有4個引數,第一個引數是原訊號,第二個是傳入的next閉包,第三個是error閉包,最後一個是completed閉包。

subscribeForever一進入這個函式就會呼叫recursiveBlock( )閉包,閉包中有一個recurse( )的入參的引數。在recursiveBlock( )閉包中對原訊號RACSignal進行訂閱。next,error,completed分別會先呼叫傳進來的閉包。然後error,completed執行完error( )和completed( )閉包之後,還會繼續再執行recurse( ),recurse( )是recursiveBlock的入參。


- (RACDisposable *)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock {
    RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];

    [self scheduleRecursiveBlock:[recursiveBlock copy] addingToDisposable:disposable];
    return disposable;
}

- (void)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock addingToDisposable:(RACCompoundDisposable *)disposable {
    @autoreleasepool {
        RACCompoundDisposable *selfDisposable = [RACCompoundDisposable compoundDisposable];
        [disposable addDisposable:selfDisposable];

        __weak RACDisposable *weakSelfDisposable = selfDisposable;

        RACDisposable *schedulingDisposable = [self schedule:^{ // 此處省略 }];

        [selfDisposable addDisposable:schedulingDisposable];
    }
}複製程式碼

先取到當前的currentScheduler,即recursiveScheduler,執行scheduleRecursiveBlock,在這個函式中,會呼叫schedule函式。這裡的recursiveScheduler是RACQueueScheduler型別的。


- (RACDisposable *)schedule:(void (^)(void))block {
    NSCParameterAssert(block != NULL);

    RACDisposable *disposable = [[RACDisposable alloc] init];

    dispatch_async(self.queue, ^{
        if (disposable.disposed) return;
        [self performAsCurrentScheduler:block];
    });

    return disposable;
}複製程式碼

如果原訊號沒有disposed,dispatch_async會繼續執行block,在這個block中還會繼續原訊號的傳送。所以原訊號只要沒有error訊號,disposable.disposed就不會返回YES,就會一直呼叫block。所以在subscribeForever的error和completed的最後都會呼叫recurse( )閉包。error呼叫recurse( )閉包是為了結束呼叫block,結束所有的訊號。completed呼叫recurse( )閉包是為了繼續呼叫block( )閉包,也就是repeat的本質。原訊號會繼續傳送訊號,如此無限迴圈下去。

14. retry:


- (RACSignal *)retry:(NSInteger)retryCount {
    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        __block NSInteger currentRetryCount = 0;
        return subscribeForever(self,
                                ^(id x) {
                                    [subscriber sendNext:x];
                                },
                                ^(NSError *error, RACDisposable *disposable) {
                                    if (retryCount == 0 || currentRetryCount < retryCount) {
                                        // Resubscribe.
                                        currentRetryCount++;
                                        return;
                                    }

                                    [disposable dispose];
                                    [subscriber sendError:error];
                                },
                                ^(RACDisposable *disposable) {
                                    [disposable dispose];
                                    [subscriber sendCompleted];
                                });
    }] setNameWithFormat:@"[%@] -retry: %lu", self.name, (unsigned long)retryCount];
}複製程式碼

在retry:的實現中,和repeat實現的區別是中間加入了一個currentRetryCount值。如果currentRetryCount > retryCount的話,就會在error中呼叫[disposable dispose],這樣subscribeForever就不會再無限迴圈下去了。

所以retry:操作的用途就是在原訊號在出現error的時候,重試retryCount的次數,如果依舊error,那麼就會停止重試。

如果原訊號沒有發生錯誤,那麼原訊號在傳送結束,subscribeForever也就結束了。retry:操作對於沒有任何error的訊號相當於什麼都沒有發生。

15. retry


- (RACSignal *)retry {
    return [[self retry:0] setNameWithFormat:@"[%@] -retry", self.name];
}複製程式碼

這裡的retry操作就是一個無限重試的操作。因為retryCount設定成0之後,在error的閉包中中,retryCount 永遠等於 0,原訊號永遠都不會被dispose,所以subscribeForever會一直無限重試下去。

同樣的,如果對一個沒有error的訊號呼叫retry操作,也是不起任何作用的。

16. scanWithStart: reduceWithIndex: (在父類RACStream中定義的)

先寫出測試程式碼:


    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber)
                         {
                             [subscriber sendNext:@1];
                             [subscriber sendNext:@1];
                             [subscriber sendNext:@4];
                             return [RACDisposable disposableWithBlock:^{
                             }];
                         }];

    RACSignal *signalB = [signalA scanWithStart:@(2) reduceWithIndex:^id(NSNumber * running, NSNumber * next, NSUInteger index) {
        return @(running.intValue * next.intValue + index);
    }];複製程式碼

2    // 2 * 1 + 0 = 2
3    // 2 * 1 + 1 = 3
14   // 3 * 4 + 2 = 14複製程式碼

ReactiveCocoa 中 RACSignal 所有變換操作底層實現分析(上)



- (instancetype)scanWithStart:(id)startingValue reduceWithIndex:(id (^)(id, id, NSUInteger))reduceBlock {
    NSCParameterAssert(reduceBlock != nil);

    Class class = self.class;

    return [[self bind:^{
        __block id running = startingValue;
        __block NSUInteger index = 0;

        return ^(id value, BOOL *stop) {
            running = reduceBlock(running, value, index++);
            return [class return:running];
        };
    }] setNameWithFormat:@"[%@] -scanWithStart: %@ reduceWithIndex:", self.name, [startingValue rac_description]];
}複製程式碼

scanWithStart這個變換由初始值,變換函式reduceBlock( ),和index步進的變數組成。原訊號的每個訊號都會由變換函式reduceBlock( )進行變換。index每次都是自增。變換的初始值是由入參startingValue傳入的。

17. scanWithStart: reduce: (在父類RACStream中定義的)


- (instancetype)scanWithStart:(id)startingValue reduce:(id (^)(id running, id next))reduceBlock {
    NSCParameterAssert(reduceBlock != nil);

    return [[self
             scanWithStart:startingValue
             reduceWithIndex:^(id running, id next, NSUInteger index) {
                 return reduceBlock(running, next);
             }]
            setNameWithFormat:@"[%@] -scanWithStart: %@ reduce:", self.name, [startingValue rac_description]];
}複製程式碼

ReactiveCocoa 中 RACSignal 所有變換操作底層實現分析(上)

scanWithStart: reduce:就是scanWithStart: reduceWithIndex: 的縮略版。變換函式也是外面閉包reduceBlock( )傳進來的。只不過變換過程中不會使用index自增的這個變數。

18. aggregateWithStart: reduceWithIndex:


- (RACSignal *)aggregateWithStart:(id)start reduceWithIndex:(id (^)(id, id, NSUInteger))reduceBlock {
    return [[[[self
               scanWithStart:start reduceWithIndex:reduceBlock]
              startWith:start]
             takeLast:1]
            setNameWithFormat:@"[%@] -aggregateWithStart: %@ reduceWithIndex:", self.name, [start rac_description]];
}複製程式碼

ReactiveCocoa 中 RACSignal 所有變換操作底層實現分析(上)

aggregate是合計的意思。所以最後變換出來的訊號只有最後一個值。
aggregateWithStart: reduceWithIndex:操作呼叫了scanWithStart: reduceWithIndex:,原理和它完全一致。不同的是多了兩步額外的操作,一個是startWith:,一個是takeLast:1。startWith:是在scanWithStart: reduceWithIndex:變換之後的訊號之前加上start訊號。takeLast:1是取最後一個訊號。takeLast:和startWith:的詳細分析文章下面會詳述。

值得注意的一點是,原訊號如果沒有傳送complete訊號,那麼該函式就不會輸出新的訊號值。因為在一直等待結束。

19. aggregateWithStart: reduce:


- (RACSignal *)aggregateWithStart:(id)start reduce:(id (^)(id running, id next))reduceBlock {
    return [[self
             aggregateWithStart:start
             reduceWithIndex:^(id running, id next, NSUInteger index) {
                 return reduceBlock(running, next);
             }]
            setNameWithFormat:@"[%@] -aggregateWithStart: %@ reduce:", self.name, [start rac_description]];
}複製程式碼

ReactiveCocoa 中 RACSignal 所有變換操作底層實現分析(上)

aggregateWithStart: reduce:呼叫aggregateWithStart: reduceWithIndex:函式,只不過沒有隻用index值。同樣,如果原訊號沒有傳送complete訊號,也不會輸出任何訊號。

20. aggregateWithStartFactory: reduce:


- (RACSignal *)aggregateWithStartFactory:(id (^)(void))startFactory reduce:(id (^)(id running, id next))reduceBlock {
    NSCParameterAssert(startFactory != NULL);
    NSCParameterAssert(reduceBlock != NULL);

    return [[RACSignal defer:^{
        return [self aggregateWithStart:startFactory() reduce:reduceBlock];
    }] setNameWithFormat:@"[%@] -aggregateWithStartFactory:reduce:", self.name];
}複製程式碼

aggregateWithStartFactory: reduce:內部實現就是呼叫aggregateWithStart: reduce:,只不過入參多了一個產生start的startFactory( )閉包罷了。

21. collect


- (RACSignal *)collect {
    return [[self aggregateWithStartFactory:^{
        return [[NSMutableArray alloc] init];
    } reduce:^(NSMutableArray *collectedValues, id x) {
        [collectedValues addObject:(x ?: NSNull.null)];
        return collectedValues;
    }] setNameWithFormat:@"[%@] -collect", self.name];
}複製程式碼

ReactiveCocoa 中 RACSignal 所有變換操作底層實現分析(上)

collect函式會呼叫aggregateWithStartFactory: reduce:方法。把所有原訊號的值收集起來,儲存在NSMutableArray中。

二. 時間操作

ReactiveCocoa 中 RACSignal 所有變換操作底層實現分析(上)

1. throttle:valuesPassingTest:

這個操作傳入一個時間間隔NSTimeInterval,和一個判斷條件的閉包predicate。原訊號在一個時間間隔NSTimeInterval之間傳送的訊號,如果還能滿足predicate,則原訊號都被“吞”了,直到一個時間間隔NSTimeInterval結束,會再次判斷predicate,如果不滿足了,原訊號就會被髮送出來。

ReactiveCocoa 中 RACSignal 所有變換操作底層實現分析(上)

如上圖,原訊號傳送1以後,間隔NSTimeInterval的時間內,沒有訊號發出,並且predicate也為YES,就把1變換成新的訊號發出去。接下去由於原訊號傳送2,3,4的過程中,都在間隔NSTimeInterval的時間內,所以都被“吞”了。直到原訊號傳送5之後,間隔NSTimeInterval的時間內沒有新的訊號發出,所以把原訊號的5傳送出來。原訊號的6也是如此。

再來看看具體實現:


- (RACSignal *)throttle:(NSTimeInterval)interval valuesPassingTest:(BOOL (^)(id next))predicate {
    NSCParameterAssert(interval >= 0);
    NSCParameterAssert(predicate != nil);

    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable];

        RACScheduler *scheduler = [RACScheduler scheduler];

        __block id nextValue = nil;
        __block BOOL hasNextValue = NO;
        RACSerialDisposable *nextDisposable = [[RACSerialDisposable alloc] init];

        void (^flushNext)(BOOL send) = ^(BOOL send) { // 暫時省略 };

        RACDisposable *subscriptionDisposable = [self subscribeNext:^(id x) {
            // 暫時省略
        } error:^(NSError *error) {
            [compoundDisposable dispose];
            [subscriber sendError:error];
        } completed:^{
            flushNext(YES);
            [subscriber sendCompleted];
        }];

        [compoundDisposable addDisposable:subscriptionDisposable];
        return compoundDisposable;
    }] setNameWithFormat:@"[%@] -throttle: %f valuesPassingTest:", self.name, (double)interval];
}複製程式碼

看這段實現,裡面有2處斷言。會先判斷傳入的interval是否大於0,小於0當然是不行的。還有一個就是傳入的predicate閉包不能為空,這個是接下來用來控制流程的。

接下來的實現還是按照套路來,返回值是一個訊號,新訊號的閉包裡面再訂閱原訊號進行變換。

那麼整個變換的重點就落在了flushNext閉包和訂閱原訊號subscribeNext閉包中了。

當新的訊號一旦被訂閱,閉包執行到此處,就會對原訊號進行訂閱。


[self subscribeNext:^(id x) {
    RACScheduler *delayScheduler = RACScheduler.currentScheduler ?: scheduler;
    BOOL shouldThrottle = predicate(x);

    @synchronized (compoundDisposable) {   
        flushNext(NO);
        if (!shouldThrottle) {
            [subscriber sendNext:x];
            return;
        }
        nextValue = x;
        hasNextValue = YES;
        nextDisposable.disposable = [delayScheduler afterDelay:interval schedule:^{
            flushNext(YES);
        }];
    }
}複製程式碼
  1. 首先先建立一個delayScheduler。先判斷當前的currentScheduler是否存在,不存在就取之前建立的[RACScheduler scheduler]。這裡雖然兩處都是RACTargetQueueScheduler型別的,但是currentScheduler是com.ReactiveCocoa.RACScheduler.mainThreadScheduler,而[RACScheduler scheduler]建立的是com.ReactiveCocoa.RACScheduler.backgroundScheduler。

  2. 呼叫predicate( )閉包,傳入原訊號發來的訊號值x,經過predicate判斷以後,得到是否開啟節流開關的BOOL變數shouldThrottle。

  3. 之所以把RACCompoundDisposable作為執行緒間互斥訊號量,因為RACCompoundDisposable裡面會加入所有的RACDisposable訊號。接著下面的操作用@synchronized給執行緒間加鎖。

  4. flushNext( )這個閉包是為了hook住原訊號的傳送。


void (^flushNext)(BOOL send) = ^(BOOL send) {
    @synchronized (compoundDisposable) {
        [nextDisposable.disposable dispose];

        if (!hasNextValue) return;
        if (send) [subscriber sendNext:nextValue];

        nextValue = nil;
        hasNextValue = NO;
    }
};複製程式碼

這個閉包中如果傳入的是NO,那麼原訊號就無法立即sendNext。如果傳入的是YES,並且hasNextValue = YES,原訊號待傳送的還有值,那麼就傳送原訊號。

shouldThrottle是一個閥門,隨時控制原訊號是否可以被髮送。

小結一下,每個原訊號傳送過來,通過在throttle:valuesPassingTest:裡面的did subscriber閉包中進行訂閱。這個閉包中主要乾了4件事情:

  1. 呼叫flushNext(NO)閉包判斷能否傳送原訊號的值。入參為NO,不傳送原訊號的值。
  2. 判斷閥門條件predicate(x)能否傳送原訊號的值。
  3. 如果以上兩個條件都滿足,nextValue中進行賦值為原訊號發來的值,hasNextValue = YES代表當前有要傳送的值。
  4. 開啟一個delayScheduler,延遲interval的時間,傳送原訊號的這個值,即呼叫flushNext(YES)。

現在再來分析一下整個throttle:valuesPassingTest:的全過程

原訊號發出第一個值,如果在interval的時間間隔內,沒有新的訊號傳送,那麼delayScheduler延遲interval的時間,執行flushNext(YES),傳送原訊號的這個第一個值。


- (RACDisposable *)afterDelay:(NSTimeInterval)delay schedule:(void (^)(void))block {
    return [self after:[NSDate dateWithTimeIntervalSinceNow:delay] schedule:block];
}

- (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block {
    NSCParameterAssert(date != nil);
    NSCParameterAssert(block != NULL);

    RACDisposable *disposable = [[RACDisposable alloc] init];

    dispatch_after([self.class wallTimeWithDate:date], self.queue, ^{
        if (disposable.disposed) return;
        [self performAsCurrentScheduler:block];
    });

    return disposable;
}複製程式碼

注意,在dispatch_after閉包裡面之前[self performAsCurrentScheduler:block]之前,有一個關鍵的判斷:


if (disposable.disposed) return;複製程式碼

這個判斷就是用來判斷從第一個訊號發出,在間隔interval的時間之內,還有沒有其他訊號存在。如果有,第一個訊號肯定會disposed,這裡會執行return,所以也就不會把第一個訊號傳送出來了。

這樣也就達到了節流的目的:原來每個訊號都會建立一個delayScheduler,都會延遲interval的時間,在這個時間內,如果原訊號再沒有傳送新值,即原訊號沒有disposed,就把原訊號的值發出來;如果在這個時間內,原訊號還傳送了一個新值,那麼第一個值就被丟棄。在傳送過程中,每個訊號都要判斷一次predicate( ),這個是閥門的開關,如果隨時都不節流了,原訊號發的值就需要立即被髮送出來。

還有二點需要注意的是,第一點,正好在interval那一時刻,有新訊號傳送出來,原訊號也會被丟棄,即只有在>=interval的時間之內,原訊號沒有傳送新值,原來的這個值才能傳送出來。第二點,原訊號傳送completed時,會立即執行flushNext(YES),把原訊號的最後一個值傳送出來。

2. throttle:


- (RACSignal *)throttle:(NSTimeInterval)interval {
    return [[self throttle:interval valuesPassingTest:^(id _) {
        return YES;
    }] setNameWithFormat:@"[%@] -throttle: %f", self.name, (double)interval];
}複製程式碼

這個操作其實就是呼叫了throttle:valuesPassingTest:方法,傳入時間間隔interval,predicate( )閉包則永遠返回YES,原訊號的每個訊號都執行節流操作。

3. bufferWithTime:onScheduler:

這個操作的實現是類似於throttle:valuesPassingTest:的實現。



- (RACSignal *)bufferWithTime:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler {
    NSCParameterAssert(scheduler != nil);
    NSCParameterAssert(scheduler != RACScheduler.immediateScheduler);

    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        RACSerialDisposable *timerDisposable = [[RACSerialDisposable alloc] init];
        NSMutableArray *values = [NSMutableArray array];

        void (^flushValues)() = ^{
            // 暫時省略
        };

        RACDisposable *selfDisposable = [self subscribeNext:^(id x) {
            // 暫時省略
        } error:^(NSError *error) {
            [subscriber sendError:error];
        } completed:^{
            flushValues();
            [subscriber sendCompleted];
        }];

        return [RACDisposable disposableWithBlock:^{
            [selfDisposable dispose];
            [timerDisposable dispose];
        }];
    }] setNameWithFormat:@"[%@] -bufferWithTime: %f onScheduler: %@", self.name, (double)interval, scheduler];
}複製程式碼

bufferWithTime:onScheduler:的實現和throttle:valuesPassingTest:的實現給出類似。開始有2個斷言,2個都是判斷scheduler的,第一個斷言是判斷scheduler是否為nil。第二個斷言是判斷scheduler的型別的,scheduler型別不能是immediateScheduler型別的,因為這個方法是要快取一些訊號的,所以不能是immediateScheduler型別的。


RACDisposable *selfDisposable = [self subscribeNext:^(id x) {
    @synchronized (values) {
        if (values.count == 0) {
            timerDisposable.disposable = [scheduler afterDelay:interval schedule:flushValues];
        }
        [values addObject:x ?: RACTupleNil.tupleNil];
    }
}複製程式碼

在subscribeNext中,當陣列裡面是沒有存任何原訊號的值,就會開啟一個scheduler,延遲interval時間,執行flushValues閉包。如果裡面有值了,就繼續加到values的陣列中。關鍵的也是閉包裡面的內容,程式碼如下:


void (^flushValues)() = ^{
    @synchronized (values) {
        [timerDisposable.disposable dispose];

        if (values.count == 0) return;

        RACTuple *tuple = [RACTuple tupleWithObjectsFromArray:values];
        [values removeAllObjects];
        [subscriber sendNext:tuple];
    }
};複製程式碼

flushValues( )閉包裡面主要是把陣列包裝成一個元組,並且全部傳送出來,原陣列裡面就全部清空了。這也是bufferWithTime:onScheduler:的作用,在interval時間內,把這個時間間隔內的原訊號都快取起來,並且在interval的那一刻,把這些快取的訊號打包成一個元組,傳送出來。

和throttle:valuesPassingTest:方法一樣,在原訊號completed的時候,立即執行flushValues( )閉包,把裡面存的值都傳送出來。

4. delay:

delay:函式的操作和上面幾個套路都是一樣的,實現方式也都是模板式的,唯一的不同都在subscribeNext中,和一個判斷是否傳送的閉包中。


- (RACSignal *)delay:(NSTimeInterval)interval {
    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];

        // We may never use this scheduler, but we need to set it up ahead of
        // time so that our scheduled blocks are run serially if we do.
        RACScheduler *scheduler = [RACScheduler scheduler];

        void (^schedule)(dispatch_block_t) = ^(dispatch_block_t block) {
            // 暫時省略
        };

        RACDisposable *subscriptionDisposable = [self subscribeNext:^(id x) {
            // 暫時省略
        } error:^(NSError *error) {
            [subscriber sendError:error];
        } completed:^{
            schedule(^{
                [subscriber sendCompleted];
            });
        }];

        [disposable addDisposable:subscriptionDisposable];
        return disposable;
    }] setNameWithFormat:@"[%@] -delay: %f", self.name, (double)interval];
}複製程式碼

在delay:的subscribeNext中,就單純的執行了schedule的閉包。


        RACDisposable *subscriptionDisposable = [self subscribeNext:^(id x) {
            schedule(^{
                [subscriber sendNext:x];
            });
        }複製程式碼

  void (^schedule)(dispatch_block_t) = ^(dispatch_block_t block) {
          RACScheduler *delayScheduler = RACScheduler.currentScheduler ?: scheduler;
          RACDisposable *schedulerDisposable = [delayScheduler afterDelay:interval schedule:block];
          [disposable addDisposable:schedulerDisposable];
      };複製程式碼

在schedule閉包中做的時間就是延遲interval的時間傳送原訊號的值。

5. interval:onScheduler:withLeeway:


+ (RACSignal *)interval:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler withLeeway:(NSTimeInterval)leeway {
    NSCParameterAssert(scheduler != nil);
    NSCParameterAssert(scheduler != RACScheduler.immediateScheduler);

    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        return [scheduler after:[NSDate dateWithTimeIntervalSinceNow:interval] repeatingEvery:interval withLeeway:leeway schedule:^{
            [subscriber sendNext:[NSDate date]];
        }];
    }] setNameWithFormat:@"+interval: %f onScheduler: %@ withLeeway: %f", (double)interval, scheduler, (double)leeway];
}複製程式碼

在這個操作中,實現程式碼不難。先來看看2個斷言,都是保護入參型別的,scheduler不能為空,且不能是immediateScheduler的型別,原因和上面是一樣的,這裡是延遲操作。

主要的實現就在after:repeatingEvery:withLeeway:schedule:上了。


- (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block {
    NSCParameterAssert(date != nil);
    NSCParameterAssert(interval > 0.0 && interval < INT64_MAX / NSEC_PER_SEC);
    NSCParameterAssert(leeway >= 0.0 && leeway < INT64_MAX / NSEC_PER_SEC);
    NSCParameterAssert(block != NULL);

    uint64_t intervalInNanoSecs = (uint64_t)(interval * NSEC_PER_SEC);
    uint64_t leewayInNanoSecs = (uint64_t)(leeway * NSEC_PER_SEC);

    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.queue);
    dispatch_source_set_timer(timer, [self.class wallTimeWithDate:date], intervalInNanoSecs, leewayInNanoSecs);
    dispatch_source_set_event_handler(timer, block);
    dispatch_resume(timer);

    return [RACDisposable disposableWithBlock:^{
        dispatch_source_cancel(timer);
    }];
}複製程式碼

這裡的實現就是用GCD在self.queue上建立了一個Timer,時間間隔是interval,修正時間是leeway。

leeway這個引數是為dispatch source指定一個期望的定時器事件精度,讓系統能夠靈活地管理並喚醒核心。例如系統可以使用leeway值來提前或延遲觸發定時器,使其更好地與其它系統事件結合。建立自己的定時器時,應該儘量指定一個leeway值。不過就算指定leeway值為0,也不能完完全全期望定時器能夠按照精確的納秒來觸發事件。

這個定時器在interval執行sendNext操作,也就是傳送原訊號的值。

6. interval:onScheduler:



+ (RACSignal *)interval:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler {
    return [[RACSignal interval:interval onScheduler:scheduler withLeeway:0.0] setNameWithFormat:@"+interval: %f onScheduler: %@", (double)interval, scheduler];
}複製程式碼

這個操作就是呼叫上一個方法interval:onScheduler:withLeeway:,只不過leeway = 0.0。具體實現上面已經分析過了,這裡不再贅述。

最後

本來想窮盡分析每一個RACSignal的操作的實現,但是發現所有操作加起來實在太多,用一篇文章全部寫完篇幅太長了,還是拆成幾篇,RACSignal還剩下過濾操作,多訊號組合操作,冷熱訊號轉換操作,高階訊號操作,下篇接著繼續分析。最後請大家多多指教。

相關文章