别人眼中的ReactiveCocoa下

目录

  • 1.过滤操作
  • 2.组合操作

一. 过滤操作

过滤操作也属于一种变换,根据过滤条件,过滤出符合条件的值。变换出来的新的信号是原信号的一个子集。

1. filter: (在父类RACStream中定义的)

这个filter:操作在any:的实现中用到过了。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

- (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];

}

 

filter:中传入一个闭包,是用筛选的条件。如果满足筛选条件的即返回原信号的值,否则原信号的值被“吞”掉,返回空的信号。这个变换主要是用flattenMap的。

2. ignoreValues

 

 

1

2

3

4

5

- (RACSignal *)ignoreValues {

    return [[self filter:^(id _) {

        return NO;

    }] setNameWithFormat:@"[%@] -ignoreValues", self.name];

}

 

由上面filter的实现,这里把筛选判断条件永远的传入NO,那么原信号的值都会被变换成empty信号,故变换之后的信号为空信号。

3. ignore: (在父类RACStream中定义的)

 

 

1

2

3

4

5

- (instancetype)ignore:(id)value {

    return [[self filter:^ BOOL (id innerValue) {

        return innerValue != value && ![innerValue isEqual:value];

    }] setNameWithFormat:@"[%@] -ignore: %@", self.name, [value rac_description]];

}

 

ignore:的实现还是由filter:实现的。传入的筛选判断条件是一个值,当原信号发送的值中是这个值的时候,就替换成空信号。

4. distinctUntilChanged (在父类RACStream中定义的)

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

- (instancetype)distinctUntilChanged {

    Class class = self.class;

 

    return [[self bind:^{

        __block id lastValue = nil;

        __block BOOL initial = YES;

 

        return ^(id x, BOOL *stop) {

            if (!initial && (lastValue == x || [x isEqual:lastValue])) return [class empty];

 

            initial = NO;

            lastValue = x;

            return [class return:x];

        };

    }] setNameWithFormat:@"[%@] -distinctUntilChanged", self.name];

}

 

distinctUntilChanged的实现是用bind来完成的。每次变换中都记录一下原信号上一次发送过来的值,并与这一次进行比较,如果是相同的值,就“吞”掉,返回empty信号。只有和原信号上一次发送的值不同,变换后的新信号才把这个值发送出来。

关于distinctUntilChanged,这里关注的是两两信号之间的值是否不同,有时候我们可能需要一个类似于NSSet的信号集,distinctUntilChanged就无法满足了。在ReactiveCocoa 2.5的这个版本也并没有向我们提供distinct的变换函数。

我们可以自己实现类似的变换。实现思路也不难,可以把之前每次发送过来的信号都用数组存起来,新来的信号都去数组里面查找一遍,如果找不到,就把这个值发送出去,如果找到了,就返回empty信号。效果如上图。

5. take: (在父类RACStream中定义的)

 

Objective-C

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

- (instancetype)take:(NSUInteger)count {

    Class class = self.class;

 

    if (count == 0) return class.empty;

 

    return [[self bind:^{

        __block NSUInteger taken = 0;

 

        return ^ id (id value, BOOL *stop) {

            if (taken < count) {

                ++taken;

                if (taken == count) *stop = YES;

                return [class return:value];

            } else {

                return nil;

            }

        };

    }] setNameWithFormat:@"[%@] -take: %lu", self.name, (unsigned long)count];

}

 

take:实现也非常简单,借助bind函数来实现的。入参的count是原信号取值的个数。在bind的闭包中,taken计数从0开始取原信号的值,当taken取到count个数的时候,就停止取值。

在take:的基础上我们还可以继续改造出新的变换方式。比如说,想取原信号中执行的第几个值。类似于elementAt的操作。这个操作在ReactiveCocoa 2.5的这个版本也并没有直接向我们提供出来。

其实实现很简单,只需要判断taken是否等于我们要取的那个位置就可以了,等于的时候把原信号的值发送出来,并*stop = YES。

Objective-C

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

// 我自己增加实现的方法

- (instancetype)elementAt:(NSUInteger)index {

    Class class = self.class;

 

    return [[self bind:^{

        __block NSUInteger taken = 0;

 

        return ^ id (id value, BOOL *stop) {

            if (index == 0) {

                *stop = YES;

                return [class return:value];

            }

            if (taken == index) {

                *stop = YES;

                return [class return:value];

            } else if (taken < index){

                taken ++;

                return [class empty];

            }else {

                return nil;

            }

        };

    }] setNameWithFormat:@"[%@] -elementAt: %lu", self.name, (unsigned long)index];

}

 

6. takeLast:

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

- (RACSignal *)takeLast:(NSUInteger)count {

    return [[RACSignal createSignal:^(id subscriber) {

        NSMutableArray *valuesTaken = [NSMutableArray arrayWithCapacity:count];

        return [self subscribeNext:^(id x) {

            [valuesTaken addObject:x ? : RACTupleNil.tupleNil];

 

            while (valuesTaken.count > count) {

                [valuesTaken removeObjectAtIndex:0];

            }

        } error:^(NSError *error) {

            [subscriber sendError:error];

        } completed:^{

            for (id value in valuesTaken) {

                [subscriber sendNext:value == RACTupleNil.tupleNil ? nil : value];

            }

 

            [subscriber sendCompleted];

        }];

    }] setNameWithFormat:@"[%@] -takeLast: %lu", self.name, (unsigned long)count];

}

 

takeLast:的实现也是按照套路来。先创建一个新信号,return的时候订阅原信号。在函数内部用一个valuesTaken来保存原信号发送过来的值,原信号发多少,就存多少,直到个数溢出入参给定的count,就溢出数组第0位。这样能保证数组里面始终都装着最后count个原信号的值。

当原信号发送completed信号的时候,把数组里面存的值都sendNext出去。这里要注意的也是该变换发送信号的时机。如果原信号一直没有completed,那么takeLast:就一直没法发出任何信号来。

7. takeUntilBlock: (在父类RACStream中定义的)

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

- (instancetype)takeUntilBlock:(BOOL (^)(id x))predicate {

    NSCParameterAssert(predicate != nil);

 

    Class class = self.class;

 

    return [[self bind:^{

        return ^ id (id value, BOOL *stop) {

            if (predicate(value)) return nil;

 

            return [class return:value];

        };

    }] setNameWithFormat:@"[%@] -takeUntilBlock:", self.name];

}

takeUntilBlock:是根据传入的predicate闭包作为筛选条件的。一旦predicate( )闭包满足条件,那么新信号停止发送新信号,因为它被置为nil了。和函数名的意思是一样的,take原信号的值,Until直到闭包满足条件。

8. takeWhileBlock: (在父类RACStream中定义的)

 

 

1

2

3

4

5

6

7

- (instancetype)takeWhileBlock:(BOOL (^)(id x))predicate {

    NSCParameterAssert(predicate != nil);

 

    return [[self takeUntilBlock:^ BOOL (id x) {

        return !predicate(x);

    }] setNameWithFormat:@"[%@] -takeWhileBlock:", self.name];

}

takeWhileBlock:的信号集是takeUntilBlock:的信号集的补集。全集是原信号。takeWhileBlock:底层还是调用takeUntilBlock:,只不过判断条件的是不满足predicate( )闭包的集合。

9. takeUntil:

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

- (RACSignal *)takeUntil:(RACSignal *)signalTrigger {

    return [[RACSignal createSignal:^(id subscriber) {

        RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];

        void (^triggerCompletion)(void) = ^{

            [disposable dispose];

            [subscriber sendCompleted];

        };

 

        RACDisposable *triggerDisposable = [signalTrigger subscribeNext:^(id _) {

            triggerCompletion();

        } completed:^{

            triggerCompletion();

        }];

 

        [disposable addDisposable:triggerDisposable];

 

        if (!disposable.disposed) {

            RACDisposable *selfDisposable = [self subscribeNext:^(id x) {

                [subscriber sendNext:x];

            } error:^(NSError *error) {

                [subscriber sendError:error];

            } completed:^{

                [disposable dispose];

                [subscriber sendCompleted];

            }];

 

            [disposable addDisposable:selfDisposable];

        }

 

        return disposable;

    }] setNameWithFormat:@"[%@] -takeUntil: %@", self.name, signalTrigger];

}

 

takeUntil:的实现也是“经典套路”——return一个新信号,在新信号中订阅原信号。入参是一个信号signalTrigger,这个信号是一个Trigger。一旦signalTrigger发出第一个信号,就会触发triggerCompletion( )闭包,在这个闭包中,会调用triggerCompletion( )闭包。

 

1

2

3

4

  void (^triggerCompletion)(void) = ^{

   [disposable dispose];

   [subscriber sendCompleted];

  };

一旦调用了triggerCompletion( )闭包,就会把原信号取消订阅,并给变换的新的信号订阅者sendCompleted。

如果入参signalTrigger一直没有sendNext,那么原信号就会一直sendNext:。

10. takeUntilReplacement:

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

- (RACSignal *)takeUntilReplacement:(RACSignal *)replacement {

    return [RACSignal createSignal:^(id subscriber) {

        RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init];

 

        RACDisposable *replacementDisposable = [replacement subscribeNext:^(id x) {

            [selfDisposable dispose];

            [subscriber sendNext:x];

        } error:^(NSError *error) {

            [selfDisposable dispose];

            [subscriber sendError:error];

        } completed:^{

            [selfDisposable dispose];

            [subscriber sendCompleted];

        }];

 

        if (!selfDisposable.disposed) {

            selfDisposable.disposable = [[self

                                          concat:[RACSignal never]]

                                         subscribe:subscriber];

        }

 

        return [RACDisposable disposableWithBlock:^{

            [selfDisposable dispose];

            [replacementDisposable dispose];

        }];

    }];

}

 

 

  1. 原信号concat:了一个[RACSignal never]信号,这样原信号就一直不会disposed,会一直等待replacement信号的到来。
  2. 控制selfDisposable是否被dispose,控制权来自于入参的replacement信号,一旦replacement信号sendNext,那么原信号就会取消订阅,接下来的事情就会交给replacement信号了。
  3. 变换后的新信号sendNext,sendError,sendCompleted全部都由replacement信号来发送,最终新信号完成的时刻也是replacement信号完成的时刻。

11. skip: (在父类RACStream中定义的)

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

- (instancetype)skip:(NSUInteger)skipCount {

    Class class = self.class;

 

    return [[self bind:^{

        __block NSUInteger skipped = 0;

 

        return ^(id value, BOOL *stop) {

            if (skipped >= skipCount) return [class return:value];

 

            skipped++;

            return class.empty;

        };

    }] setNameWithFormat:@"[%@] -skip: %lu", self.name, (unsigned long)skipCount];

}

 

skip:信号集和take:信号集是补集关系,全集是原信号。take:是取原信号的前count个信号,而skip:是从原信号第count + 1位开始取信号。

skipped是一个游标,每次原信号发送一个值,就比较它和入参skipCount的大小。如果不比skipCount大,说明还需要跳过,所以就返回empty信号,否则就把原信号的值发送出来。

通过类比take系列方法,可以发现在ReactiveCocoa 2.5的这个版本也并没有向我们提供skipLast:的变换函数。这个变换函数的实现过程也不难,我们可以类比takeLast:来实现。

实现的思路也不难,原信号每次发送过来的值,都用一个数组存储起来。skipLast:是想去掉原信号最末尾的count个信号。

我们先来分析一下:假设原信号有n个信号,从0 – (n-1),去掉最后的count个,前面还剩n – count个信号。那么从 原信号的第 count + 1位的信号开始发送,到原信号结束,这样中间就正好是发送了 n – count 个信号。

分析清楚后,代码就很容易了:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

// 我自己增加实现的方法

- (RACSignal *)skipLast:(NSUInteger)count {

    return [[RACSignal createSignal:^(id subscriber) {

        NSMutableArray *valuesTaken = [NSMutableArray arrayWithCapacity:count];

        return [self subscribeNext:^(id x) {

            [valuesTaken addObject:x ? : RACTupleNil.tupleNil];

 

            while (valuesTaken.count > count) {

                [subscriber sendNext:valuesTaken[0] == RACTupleNil.tupleNil ? nil : valuesTaken[0]];

                [valuesTaken removeObjectAtIndex:0];

            }

        } error:^(NSError *error) {

            [subscriber sendError:error];

        } completed:^{            

            [subscriber sendCompleted];

        }];

    }] setNameWithFormat:@"[%@] -skipLast: %lu", self.name, (unsigned long)count];

}

 

原信号每发送过来一个信号就存入数组,当数组里面的个数大于count的时候,就是需要我们发送信号的时候,这个时候每次都把数组里面第0位发送出去即可,数组维护了一个FIFO的队列。这样就实现了skipLast:的效果了。

12. skipUntilBlock: (在父类RACStream中定义的)

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

- (instancetype)skipUntilBlock:(BOOL (^)(id x))predicate {

    NSCParameterAssert(predicate != nil);

 

    Class class = self.class;

 

    return [[self bind:^{

        __block BOOL skipping = YES;

 

        return ^ id (id value, BOOL *stop) {

            if (skipping) {

                if (predicate(value)) {

                    skipping = NO;

                } else {

                    return class.empty;

                }

            }

 

            return [class return:value];

        };

    }] setNameWithFormat:@"[%@] -skipUntilBlock:", self.name];

}

skipUntilBlock: 的实现可以类比takeUntilBlock: 的实现。

skipUntilBlock: 是根据传入的predicate闭包作为筛选条件的。一旦predicate( )闭包满足条件,那么skipping = NO。skipping为NO,以后原信号发送的每个值都原封不动的发送出去。predicate( )闭包不满足条件的时候,即会一直skip原信号的值。和函数名的意思是一样的,skip原信号的值,Until直到闭包满足条件,就不再skip了。

13. skipWhileBlock: (在父类RACStream中定义的)

 

 

1

2

3

4

5

6

7

- (instancetype)skipWhileBlock:(BOOL (^)(id x))predicate {

    NSCParameterAssert(predicate != nil);

 

    return [[self skipUntilBlock:^ BOOL (id x) {

        return !predicate(x);

    }] setNameWithFormat:@"[%@] -skipWhileBlock:", self.name];

}

skipWhileBlock:的信号集是skipUntilBlock:的信号集的补集。全集是原信号。skipWhileBlock:底层还是调用skipUntilBlock:,只不过判断条件的是不满足predicate( )闭包的集合。

到这里skip系列方法就结束了,对比take系列的方法,少了2个方法,在ReactiveCocoa 2.5的这个版本中 takeUntil: 和 takeUntilReplacement:这两个方法没有与之对应的skip方法。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

// 我自己增加实现的方法

- (RACSignal *)skipUntil:(RACSignal *)signalTrigger {

    return [[RACSignal createSignal:^(id subscriber) {

        RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];

 

        __block BOOL sendTrigger = NO;

 

        void (^triggerCompletion)(void) = ^{

            sendTrigger = YES;

        };

 

        RACDisposable *triggerDisposable = [signalTrigger subscribeNext:^(id _) {

            triggerCompletion();

        } completed:^{

            triggerCompletion();

        }];

 

        [disposable addDisposable:triggerDisposable];

 

        if (!disposable.disposed) {

            RACDisposable *selfDisposable = [self subscribeNext:^(id x) {

 

                if (sendTrigger) {

                    [subscriber sendNext:x];

                }

 

            } error:^(NSError *error) {

                [subscriber sendError:error];

            } completed:^{

                [disposable dispose];

                [subscriber sendCompleted];

            }];

 

            [disposable addDisposable:selfDisposable];

        }

 

        return disposable;

    }] setNameWithFormat:@"[%@] -skipUntil: %@", self.name, signalTrigger];

}

 

skipUntil实现方法也很简单,当入参的signalTrigger开发发送信号的时候,就让原信号sendNext把值发送出来,否则就把原信号的值“吞”掉。

skipUntilReplacement:就没什么意义了,把原信号经过skipUntilReplacement:变换之后得到的新的信号就是Replacement信号。所以说这个操作也就没意义了。

14. groupBy:transform:

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

- (RACSignal *)groupBy:(id (^)(id object))keyBlock transform:(id (^)(id object))transformBlock {

    NSCParameterAssert(keyBlock != NULL);

 

    return [[RACSignal createSignal:^(id subscriber) {

        NSMutableDictionary *groups = [NSMutableDictionary dictionary];

        NSMutableArray *orderedGroups = [NSMutableArray array];

 

        return [self subscribeNext:^(id x) {

            id key = keyBlock(x);

            RACGroupedSignal *groupSubject = nil;

            @synchronized(groups) {

                groupSubject = groups[key];

                if (groupSubject == nil) {

                    groupSubject = [RACGroupedSignal signalWithKey:key];

                    groups[key] = groupSubject;

                    [orderedGroups addObject:groupSubject];

                    [subscriber sendNext:groupSubject];

                }

            }

 

            [groupSubject sendNext:transformBlock != NULL ? transformBlock(x) : x];

        } error:^(NSError *error) {

            [subscriber sendError:error];

 

            [orderedGroups makeObjectsPerformSelector:@selector(sendError:) withObject:error];

        } completed:^{

            [subscriber sendCompleted];

 

            [orderedGroups makeObjectsPerformSelector:@selector(sendCompleted)];

        }];

    }] setNameWithFormat:@"[%@] -groupBy:transform:", self.name];

}

看groupBy:transform:的实现,依旧是老“套路”。return 一个新的RACSignal,在新的信号里面订阅原信号。

groupBy:transform:的重点就在subscribeNext中了。

  1. 首先解释一下两个入参。两个入参都是闭包,keyBlock返回值是要作为字典的key,transformBlock的返回值是对原信号发出来的值x进行变换。
  2. 先创建一个NSMutableDictionary字典groups,和NSMutableArray数组orderedGroups。
  3. 从字典里面取出key对应的value,这里的key对应着keyBlock返回值。value的值是一个RACGroupedSignal信号。如果找不到对应的key值,就新建一个RACGroupedSignal信号,并存入字典对应的key值,与之对应。
  4. 新变换之后的信号,订阅之后,RACGroupedSignal进行sendNext,这是一个信号,如果transformBlock不为空,就发送transformBlock变换之后的值。
  5. sendError和sendCompleted都要分别对数组orderedGroups里面每个RACGroupedSignal都要进行sendError或者sendCompleted。因为要对数组里面每个信号都执行一个操作,所以需要调用makeObjectsPerformSelector:withObject:方法。

经过groupBy:transform:变换之后,原信号会根据keyBlock进行分组。

写出测试代码,来看看平时应该怎么用。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber)

                         {

                             [subscriber sendNext:@1];

                             [subscriber sendNext:@2];

                             [subscriber sendNext:@3];

                             [subscriber sendNext:@4];

                             [subscriber sendNext:@5];

                             [subscriber sendCompleted];

                             return [RACDisposable disposableWithBlock:^{

                                 NSLog(@"signal dispose");

                             }];

                         }];

 

    RACSignal *signalGroup = [signalA groupBy:^id(NSNumber *object) {

        return object.integerValue > 3 ? @"good" : @"bad";

    } transform:^id(NSNumber * object) {

        return @(object.integerValue * 10);

    }];

 

    [[[signalGroup filter:^BOOL(RACGroupedSignal *value) {

        return [(NSString *)value.key isEqualToString:@"good"];

    }] flatten]subscribeNext:^(id x) {

        NSLog(@"subscribeNext: %@", x);

    }];

假设原信号发送的1,2,3,4,5是代表的成绩的5个等级。当成绩大于3的都算“good”,小于3的都算“bad”。

signalGroup是原信号signalA经过groupBy:transform:得到的新的信号,这个信号是一个高阶的信号,因为它里面并不是直接装的是值,signalGroup这个信号里面装的还是信号。signalGroup里面有两个分组,分别是“good”分组和“bad”分组。

想从中取出这两个分组里面的值,需要进行一次filter:筛选。筛选之后得到对应分组的高阶信号。这时还要再进行一个flatten操作,把高阶信号变成低阶信号,再次订阅才能取到其中的值。

订阅新信号的值,输出如下:

 

1

2

subscribeNext: 40

subscribeNext: 50

关于flatten的实现:

 

1

2

3

4

5

6

- (instancetype)flatten {

    __weak RACStream *stream __attribute__((unused)) = self;

    return [[self flattenMap:^(id value) {

        return value;

    }] setNameWithFormat:@"[%@] -flatten", self.name];

}

flatten操作就是调用了flattenMap:把值传进去了。

 

1

2

3

4

5

6

7

8

9

10

11

12

- (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];

}

flatten是把高阶信号变换成低阶信号的常用操作。flattenMap:具体实现上篇文章分析过了,这里不再赘述。

15. groupBy:

 

 

1

2

3

- (RACSignal *)groupBy:(id (^)(id object))keyBlock {

    return [[self groupBy:keyBlock transform:nil] setNameWithFormat:@"[%@] -groupBy:", self.name];

}

groupBy:操作就是groupBy:transform:的缩减版,transform传入的为nil。

关于groupBy:可以干的事情很多,可以进行很高级的分组操作。这里可以举一个例子:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

    // 简单算法题,分离数组中的相同的元素,如果元素个数大于2,则组成一个新的数组,结果得到多个包含相同元素的数组,

    // 例如[1,2,3,1,2,3]分离成[1,1],[2,2],[3,3]

    RACSignal *signal = @[@1, @2, @3, @4,@2,@3,@3,@4,@4,@4].rac_sequence.signal;

 

      NSArray * array = [[[[signal groupBy:^NSString *(NSNumber *object) {

          return [NSString stringWithFormat:@"%@",object];

      }] map:^id(RACGroupedSignal *value) {

          return [value sequence];

      }] sequence] map:^id(RACSignalSequence * value) {

          return value.array;

      }].array;

 

    for (NSNumber * num in array) {

        NSLog(@"最后的数组%@",num);

    }

 

   // 最后输出 [1,2,3,4,2,3,3,4,4,4]变成[1],[2,2],[3,3,3],[4,4,4,4]

 

二. 组合操作

1. startWith: (在父类RACStream中定义的)

 

 

1

2

3

4

5

6

- (instancetype)startWith:(id)value {

 

    return [[[self.class return:value]

             concat:self]

            setNameWithFormat:@"[%@] -startWith: %@", self.name, [value rac_description]];

}

 

startWith:的实现很简单,就是先构造一个只发送一个value的信号,然后这个信号发送完毕之后接上原信号。得到的新的信号就是在原信号前面新加了一个值。

2. concat: (在父类RACStream中定义的)

这里说的concat:是在父类RACStream中定义的。

 

1

2

3

- (instancetype)concat:(RACStream *)stream {

    return nil;

}

父类中定义的这个方法就返回一个nil,具体的实现还要子类去重写。

3. concat: (在父类RACStream中定义的)

 

 

1

2

3

4

5

6

7

8

+ (instancetype)concat:(id)streams {

    RACStream *result = self.empty;

    for (RACStream *stream in streams) {

        result = [result concat:stream];

    }

 

    return [result setNameWithFormat:@"+concat: %@", streams];

}

这个concat:后面跟着一个数组,数组里面包含这很多信号,concat:依次把这些信号concat:连接串起来。

4. merge:

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

+ (RACSignal *)merge:(id)signals {

    NSMutableArray *copiedSignals = [[NSMutableArray alloc] init];

    for (RACSignal *signal in signals) {

        [copiedSignals addObject:signal];

    }

 

    return [[[RACSignal

              createSignal:^ RACDisposable * (id subscriber) {

                  for (RACSignal *signal in copiedSignals) {

                      [subscriber sendNext:signal];

                  }

 

                  [subscriber sendCompleted];

                  return nil;

              }]

             flatten]

            setNameWithFormat:@"+merge: %@", copiedSignals];

}

 

merge:后面跟一个数组。先会新建一个数组copiedSignals,把传入的信号都装到数组里。然后依次发送数组里面的信号。由于新信号也是一个高阶信号,因为sendNext会把各个信号都依次发送出去,所以需要flatten操作把这个信号转换成值发送出去。

从上图上看,上下两个信号就像被拍扁了一样,就成了新信号的发送顺序。

5. merge:

 

 

1

2

3

4

5

- (RACSignal *)merge:(RACSignal *)signal {

    return [[RACSignal

             merge:@[ self, signal ]]

            setNameWithFormat:@"[%@] -merge: %@", self.name, signal];

}

merge:后面参数也可以跟一个信号,那么merge:就是合并这两个信号。具体实现和merge:多个信号是一样的原理。

6. zip: (在父类RACStream中定义的)

 

 

1

2

3

4

5

+ (instancetype)zip:(id)streams {

    return [[self join:streams block:^(RACStream *left, RACStream *right) {

        return [left zipWith:right];

    }] setNameWithFormat:@"+zip: %@", streams];

}

zip:后面可以跟一个数组,数组里面装的是各种信号流。

它的实现是调用了join: block: 实现的。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

+ (instancetype)join:(id)streams block:(RACStream * (^)(id, id))block {

    RACStream *current = nil;

    // 第一步

    for (RACStream *stream in streams) {

 

        if (current == nil) {

            current = [stream map:^(id x) {

                return RACTuplePack(x);

            }];

 

            continue;

        }

 

        current = block(current, stream);

    }

    // 第二步

    if (current == nil) return [self empty];

 

    return [current map:^(RACTuple *xs) {

 

        NSMutableArray *values = [[NSMutableArray alloc] init];

        // 第三步

        while (xs != nil) {

            [values insertObject:xs.last ?: RACTupleNil.tupleNil atIndex:0];

            xs = (xs.count > 1 ? xs.first : nil);

        }

        // 第四步

        return [RACTuple tupleWithObjectsFromArray:values];

    }];

}

join: block: 的实现可以分为4步:

  1. 依次打包各个信号流,把每个信号流都打包成元组RACTuple。首先第一个信号流打包成一个元组,这个元组里面就一个信号。接着把第一个元组和第二个信号执行block( )闭包里面的操作。传入的block( )闭包执行的是zipWith:的操作。这个操作是把两个信号“压”在一起。具体实现分析请看第一篇文章里面分析过的,这里就不再赘述了。得到第二个元组,里面装着是第一个元组和第二个信号。之后每次循环都执行类似的操作,再把第二个元组和第三个信号进行zipWith:操作,以此类推下去,直到所有的信号流都循环一遍。
  2. 经过第一步的循环操作之后,还是nil,那么肯定就是空信号了,就返回empty信号。
  3. 这一步是把之前第一步打包出来的结果,还原回原信号的过程。经过第一步的循环之后,current会是类似这个样子,(((1), 2), 3),第三步就是为了把这种多重元组解出来,每个信号流都依次按照顺序放在数组里。注意观察current的特点,最外层的元组,是一个值和一个元组,所以从最外层的元组开始,一层一层往里“剥”。while循环每次都取最外层元组的last,即那个单独的值,插入到数组的第0号位置,然后取出first即是里面一层的元组。然后依次循环。由于每次都插入到数组0号的位置,类似于链表的头插法,最终数组里面的顺序肯定也保证是原信号的顺序。
  4. 第四步就是把还原成原信号的顺序的数组包装成元组,返回给map操作的闭包。

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

+ (instancetype)tupleWithObjectsFromArray:(NSArray *)array {

    return [self tupleWithObjectsFromArray:array convertNullsToNils:NO];

}

 

+ (instancetype)tupleWithObjectsFromArray:(NSArray *)array convertNullsToNils:(BOOL)convert {

    RACTuple *tuple = [[self alloc] init];

 

    if (convert) {

        NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:array.count];

        for (id object in array) {

            [newArray addObject:(object == NSNull.null ? RACTupleNil.tupleNil : object)];

        }

        tuple.backingArray = newArray;

    } else {

        tuple.backingArray = [array copy];

    }

 

    return tuple;

}

在转换过程中,入参convertNullsToNils的含义是,是否把数组里面的NSNull转换成RACTupleNil。

这里转换传入的是NO,所以就是把数组原封不动的copy一份。

测试代码:

 

1

2

3

4

5

6

7

8

    RACSignal *signalD = [RACSignal interval:3 onScheduler:[RACScheduler mainThreadScheduler] withLeeway:0];

    RACSignal *signalO = [RACSignal interval:1 onScheduler:[RACScheduler mainThreadScheduler] withLeeway:0];

    RACSignal *signalE = [RACSignal interval:4 onScheduler:[RACScheduler mainThreadScheduler] withLeeway:0];

    RACSignal *signalB = [RACStream zip:@[signalD,signalO,signalE]];

 

    [signalB subscribeNext:^(id x) {

        NSLog(@"最后接收到的值 = %@",x);

    }];

打印输出:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

2016-11-29 13:07:57.349 最后接收到的值 =  (

    "2016-11-29 05:07:56 +0000",

    "2016-11-29 05:07:54 +0000",

    "2016-11-29 05:07:57 +0000"

)

 

2016-11-29 13:08:01.350 最后接收到的值 =  (

    "2016-11-29 05:07:59 +0000",

    "2016-11-29 05:07:55 +0000",

    "2016-11-29 05:08:01 +0000"

)

 

2016-11-29 13:08:05.352 最后接收到的值 =  (

    "2016-11-29 05:08:02 +0000",

    "2016-11-29 05:07:56 +0000",

    "2016-11-29 05:08:05 +0000"

)

最后输出的信号以时间最长的为主,最后接到的信号是一个元组,里面依次包含zip:数组里每个信号在一次“压”缩周期里面的值。

7. zip: reduce: (在父类RACStream中定义的)

 

 

1

2

3

4

5

6

+ (instancetype)zip:(id)streams reduce:(id (^)())reduceBlock {

    NSCParameterAssert(reduceBlock != nil);

    RACStream *result = [self zip:streams];

    if (reduceBlock != nil) result = [result reduceEach:reduceBlock];

    return [result setNameWithFormat:@"+zip: %@ reduce:", streams];

}

zip: reduce:是一个组合的方法。具体实现可以拆分成两部分,第一部分是先执行zip:,把数组里面的信号流依次都进行组合。这一过程的实现在上一个变换实现中分析过了。zip:完成之后,紧接着进行reduceEach:操作。

这里有一个判断reduceBlock是否为nil的判断,这个判断是针对老版本的“历史遗留问题”。在ReactiveCocoa 2.5之前的版本,是允许reduceBlock传入nil,这里为了防止崩溃,所以加上了这个reduceBlock是否为nil的判断。

 

1

2

3

4

5

6

7

8

9

- (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];

}

reduceEach:操作在上篇已经分析过了。它会动态的构造闭包,对原信号每个元组,执行reduceBlock( )闭包里面的方法。具体分析见上篇。一般用法如下:

 

1

2

3

   [RACStream zip:@[ stringSignal, intSignal ] reduce:^(NSString *string, NSNumber *number) {

       return [NSString stringWithFormat:@"%@: %@", string, number];

   }];

 

8. zipWith: (在父类RACStream中定义的)

 

 

1

2

3

- (instancetype)zipWith:(RACStream *)stream {

    return nil;

}

这个方法就是在父类的RACStream中定义了,具体实现还要看RACStream各个子类的实现。

它就可以类比concat:在父类中的实现,也是直接返回一个nil。

 

1

- (instancetype)concat:(RACStream *)stream { return nil;}

在第一篇中分析了concat:和zipWith:在RACSignal子类中具体实现。忘记了具体实现的可以回去看看。

9. combineLatestWith:

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

- (RACSignal *)combineLatestWith:(RACSignal *)signal {

    NSCParameterAssert(signal != nil);

 

    return [[RACSignal createSignal:^(id subscriber) {

        RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];

 

        // 初始化第一个信号的一些标志变量

        __block id lastSelfValue = nil;

        __block BOOL selfCompleted = NO;

 

        // 初始化第二个信号的一些标志变量

        __block id lastOtherValue = nil;

        __block BOOL otherCompleted = NO;

 

        // 这里是一个判断是否sendNext的闭包

        void (^sendNext)(void) = ^{ };

 

        // 订阅第一个信号

        RACDisposable *selfDisposable = [self subscribeNext:^(id x) { }];

        [disposable addDisposable:selfDisposable];

 

        // 订阅第二个信号

        RACDisposable *otherDisposable = [signal subscribeNext:^(id x) { }];

        [disposable addDisposable:otherDisposable];

 

        return disposable;

    }] setNameWithFormat:@"[%@] -combineLatestWith: %@", self.name, signal];

}

大体实现思路比较简单,在新信号里面分别订阅原信号和入参signal信号。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

RACDisposable *selfDisposable = [self subscribeNext:^(id x) {

    @synchronized (disposable) {

        lastSelfValue = x ?: RACTupleNil.tupleNil;

        sendNext();

    }

} error:^(NSError *error) {

    [subscriber sendError:error];

} completed:^{

    @synchronized (disposable) {

        selfCompleted = YES;

        if (otherCompleted) [subscriber sendCompleted];

    }

}];

先来看看原信号订阅的具体实现:

在subscribeNext闭包中,记录下原信号最新发送的x值,并保存到lastSelfValue中。从此lastSelfValue变量每次都保存原信号发送过来的最新的值。然后再调用sendNext( )闭包。

在completed闭包中,selfCompleted中记录下原信号发送完成。这是还要判断otherCompleted是否完成,即入参信号signal是否发送完成,只有两者都发送完成了,组合的新信号才能算全部发送完成。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

RACDisposable *otherDisposable = [signal subscribeNext:^(id x) {

    @synchronized (disposable) {

        lastOtherValue = x ?: RACTupleNil.tupleNil;

        sendNext();

    }

} error:^(NSError *error) {

    [subscriber sendError:error];

} completed:^{

    @synchronized (disposable) {

        otherCompleted = YES;

        if (selfCompleted) [subscriber sendCompleted];

    }

}];

这是对入参信号signal的处理实现。和原信号的处理方式完全一致。现在重点就要看看sendNext( )闭包中都做了些什么。

 

1

2

3

4

5

6

void (^sendNext)(void) = ^{

    @synchronized (disposable) {

        if (lastSelfValue == nil || lastOtherValue == nil) return;

        [subscriber sendNext:RACTuplePack(lastSelfValue, lastOtherValue)];

    }

};

在sendNext( )闭包中,如果lastSelfValue 或者 lastOtherValue 其中之一有一个为nil,就return,因为这个时候无法结合在一起。当两个信号都有值,那么就把这两个信号的最新的值打包成元组发送出来。

可以看到,每个信号每发送出来一个新的值,都会去找另外一个信号上一个最新的值进行结合。

这里可以对比一下类似的zip:操作

zip:操作是会把新来的信号的值存起来,放在数组里,然后另外一个信号发送一个值过来就和数组第0位的值相互结合成新的元组信号发送出去,并分别移除数组里面第0位的两个值。zip:能保证每次结合的值都是唯一的,不会一个原信号的值被多次结合到新的元组信号中。但是combineLatestWith:是不能保证这一点的,在原信号或者另外一个信号新信号发送前,每次发送信号都会结合当前最新的信号,这里就会有反复结合的情况。

10. combineLatest:

 

 

1

2

3

4

5

+ (RACSignal *)combineLatest:(id)signals {

    return [[self join:signals block:^(RACSignal *left, RACSignal *right) {

        return [left combineLatestWith:right];

    }] setNameWithFormat:@"+combineLatest: %@", signals];

}

combineLatest:的实现就是把入参数组里面的每个信号都调用一次join: block:方法。传入的闭包是把两个信号combineLatestWith:一下。combineLatest:的实现就是2个操作的组合。具体实现上面也都分析过,这里不再赘述。

11. combineLatest: reduce:

 

 

1

2

3

4

5

6

+ (RACSignal *)combineLatest:(id)signals reduce:(id (^)())reduceBlock {

    NSCParameterAssert(reduceBlock != nil);

    RACSignal *result = [self combineLatest:signals];

    if (reduceBlock != nil) result = [result reduceEach:reduceBlock];

    return [result setNameWithFormat:@"+combineLatest: %@ reduce:", signals];

}

combineLatest: reduce: 的实现可以类比zip: reduce:的实现。

具体实现可以拆分成两部分,第一部分是先执行combineLatest:,把数组里面的信号流依次都进行组合。这一过程的实现在上一个变换实现中分析过了。combineLatest:完成之后,紧接着进行reduceEach:操作。

这里有一个判断reduceBlock是否为nil的判断,这个判断是针对老版本的“历史遗留问题”。在ReactiveCocoa 2.5之前的版本,是允许reduceBlock传入nil,这里为了防止崩溃,所以加上了这个reduceBlock是否为nil的判断。

12. combinePreviousWithStart: reduce:(在父类RACStream中定义的)

这个方法的实现也是多个变换操作组合在一起的。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

- (instancetype)combinePreviousWithStart:(id)start reduce:(id (^)(id previous, id next))reduceBlock {

    NSCParameterAssert(reduceBlock != NULL);

    return [[[self

              scanWithStart:RACTuplePack(start)

              reduce:^(RACTuple *previousTuple, id next) {

                  id value = reduceBlock(previousTuple[0], next);

                  return RACTuplePack(next, value);

              }]

             map:^(RACTuple *tuple) {

                 return tuple[1];

             }]

            setNameWithFormat:@"[%@] -combinePreviousWithStart: %@ reduce:", self.name, [start rac_description]];

}

combinePreviousWithStart: reduce:的实现完全可以类比scanWithStart:reduce:的实现。举个例子来说明他们俩的不同。

 

1

2

3

4

5

6

7

8

9

      RACSequence *numbers = @[ @1, @2, @3, @4 ].rac_sequence;

 

      RACSignal *signalA = [numbers combinePreviousWithStart:@0 reduce:^(NSNumber *previous, NSNumber *next) {

          return @(previous.integerValue + next.integerValue);

      }].signal;

 

    RACSignal *signalB = [numbers scanWithStart:@0 reduce:^(NSNumber *previous, NSNumber *next) {

        return @(previous.integerValue + next.integerValue);

    }].signal;

signalA输出如下:

 

1

2

3

4

1

3

5

7

signalB输出如下:

 

1

2

3

4

1

3

6

10

现在应该不同点应该很明显了。combinePreviousWithStart: reduce:实现的是两两之前的加和,而scanWithStart:reduce:实现的累加。

为什么会这样呢,具体看看combinePreviousWithStart: reduce:的实现。

虽然combinePreviousWithStart: reduce:也是调用了scanWithStart:reduce:,但是初始值是RACTuplePack(start)元组,聚合reduce的过程也有所不同:

 

1

2

id value = reduceBlock(previousTuple[0], next);

return RACTuplePack(next, value);

依次调用reduceBlock( )闭包,传入previousTuple[0], next,这里reduceBlock( )闭包是进行累加的操作,所以就是把前一个元组的第0位加上后面新来的信号的值。得到的值拼成新的元组,新的元组由next和value值构成。

如果打印出上述例子中combinePreviousWithStart: reduce:的加合过程中每个信号的值,如下:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

(

    1,

    1

)

 

(

    2,

    3

)

(

    3,

    5

)

(

    4,

    7

)

由于这样拆成元组之后,下次再进行操作的时候,还可以拿到前一个信号的值,这样就不会形成累加的效果。

13. sample:

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

- (RACSignal *)sample:(RACSignal *)sampler {

    NSCParameterAssert(sampler != nil);

 

    return [[RACSignal createSignal:^(id subscriber) {

        NSLock *lock = [[NSLock alloc] init];

        __block id lastValue;

        __block BOOL hasValue = NO;

 

        RACSerialDisposable *samplerDisposable = [[RACSerialDisposable alloc] init];

        RACDisposable *sourceDisposable = [self subscribeNext:^(id x) { // 暂时省略 }];

 

        samplerDisposable.disposable = [sampler subscribeNext:^(id _) { // 暂时省略 }];

 

        return [RACDisposable disposableWithBlock:^{

            [samplerDisposable dispose];

            [sourceDisposable dispose];

        }];

    }] setNameWithFormat:@"[%@] -sample: %@", self.name, sampler];

}

sample:内部实现也是对原信号和入参信号sampler分别进行订阅。具体实现就是这两个信号订阅内部都干了些什么。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

RACSerialDisposable *samplerDisposable = [[RACSerialDisposable alloc] init];

RACDisposable *sourceDisposable = [self subscribeNext:^(id x) {

    [lock lock];

    hasValue = YES;

    lastValue = x;

    [lock unlock];

} error:^(NSError *error) {

    [samplerDisposable dispose];

    [subscriber sendError:error];

} completed:^{

    [samplerDisposable dispose];

    [subscriber sendCompleted];

}];

这是对原信号的操作,原信号的操作在subscribeNext中就记录了两个变量的值,hasValue记录原信号有值,lastValue记录了原信号的最新的值。这里加了一层NSLock锁进行保护。

在发生error的时候,先把sampler信号取消订阅,然后再sendError:。当原信号完成的时候,同样是先把sampler信号取消订阅,然后再sendCompleted。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

samplerDisposable.disposable = [sampler subscribeNext:^(id _) {

    BOOL shouldSend = NO;

    id value;

    [lock lock];

    shouldSend = hasValue;

    value = lastValue;

    [lock unlock];

 

    if (shouldSend) {

        [subscriber sendNext:value];

    }

} error:^(NSError *error) {

    [sourceDisposable dispose];

    [subscriber sendError:error];

} completed:^{

    [sourceDisposable dispose];

    [subscriber sendCompleted];

}];

这是对入参信号sampler的操作。shouldSend默认值是NO,这个变量控制着是否sendNext:值。只有当原信号有值的时候,hasValue = YES,所以shouldSend = YES,这个时候才能发送原信号的值。这里我们并不关心入参信号sampler的值,从subscribeNext:^(id _)这里可以看出, _代表并不需要它的值。

在发生error的时候,先把原信号取消订阅,然后再sendError:。当sampler信号完成的时候,同样是先把原信号取消订阅,然后再sendCompleted。

经过sample:变换就会变成这个样子。只是把原信号的值都移动到了sampler信号发送信号的时刻,值还是和原信号的值一样。

目录

  • 1.高阶信号操作
  • 2.同步操作
  • 3.副作用操作
  • 4.多线程操作
  • 5.其他操作

一. 高阶信号操作

高阶操作大部分的操作是针对高阶信号的,也就是说信号里面发送的值还是一个信号或者是一个高阶信号。可以类比数组,这里就是多维数组,数组里面还是套的数组。

1. flattenMap: (在父类RACStream中定义的)

flattenMap:在整个RAC中具有很重要的地位,很多信号变换都是可以用flattenMap:来实现的。

map:,flatten,filter,sequenceMany:这4个操作都是用flattenMap:来实现的。然而其他变换操作实现里面用到map:,flatten,filter又有很多。

回顾一下map:的实现:

 

1

2

3

4

5

6

7

8

- (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];

}

map:的操作其实就是直接原信号进行的 flattenMap:的操作,变换出来的新的信号的值是block(value)。

flatten的实现接下去会具体分析,这里先略过。

filter的实现:

 

1

2

3

4

5

6

7

8

- (instancetype)filter:(BOOL (^)(id value))block {

    NSCParameterAssert(block != nil);

 

    Class class = self.class;

    return [[self flattenMap:^ id (id value) {

        block(value) ? return [class return:value] :  return class.empty;

    }] setNameWithFormat:@"[%@] -filter:", self.name];

}

filter的实现和map:有点类似,也是对原信号进行 flattenMap:的操作,只不过block(value)不是作为返回值,而是作为判断条件,满足这个闭包的条件,变换出来的新的信号返回值就是value,不满足的就返回empty信号

接下去要分析的高阶操作里面,switchToLatest,try:,tryMap:的实现中也将会使用到flattenMap:。

flattenMap:的源码实现:

 

1

2

3

4

5

6

7

8

9

10

11

12

- (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函数,对原信号进行变换,并返回block(value)的新信号。关于bind操作的具体流程这篇文章里面已经分析过了,这里不再赘述。

从flattenMap:的源码可以看到,它是可以支持类似Promise的串行异步操作的,并且flattenMap:是满足Monad中bind部分定义的。flattenMap:没法去实现takeUntil:和take:的操作。

然而,bind操作可以实现take:的操作,bind是完全满足Monad中bind部分定义的。

2. flatten (在父类RACStream中定义的)

flatten的源码实现:

 

1

2

3

4

5

6

- (instancetype)flatten {

    __weak RACStream *stream __attribute__((unused)) = self;

    return [[self flattenMap:^(id value) {

        return value;

    }] setNameWithFormat:@"[%@] -flatten", self.name];

}

flatten操作必须是对高阶信号进行操作,如果信号里面不是信号,即不是高阶信号,那么就会崩溃。崩溃信息如下:

 

1

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Value returned from -flattenMap: is not a stream

所以flatten是对高阶信号进行的降阶操作。高阶信号每发送一次信号,经过flatten变换,由于flattenMap:操作之后,返回的新的信号的每个值就是原信号中每个信号的值。

如果对信号A,信号B,信号C进行merge:操作,可以达到和flatten一样的效果。

 

1

    [RACSignal merge:@[signalA,signalB,signalC]];

merge:操作在上篇文章分析过,再来复习一下:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

+ (RACSignal *)merge:(id)signals {

    NSMutableArray *copiedSignals = [[NSMutableArray alloc] init];

    for (RACSignal *signal in signals) {

        [copiedSignals addObject:signal];

    }

 

    return [[[RACSignal

              createSignal:^ RACDisposable * (id subscriber) {

                  for (RACSignal *signal in copiedSignals) {

                      [subscriber sendNext:signal];

                  }

 

                  [subscriber sendCompleted];

                  return nil;

              }]

             flatten]

            setNameWithFormat:@"+merge: %@", copiedSignals];

}

现在在回来看这段代码,copiedSignals虽然是一个NSMutableArray,但是它近似合成了一个上图中的高阶信号。然后这些信号们每发送出来一个信号就发给订阅者。整个操作如flatten的字面意思一样,压平。

另外,在ReactiveCocoa v2.5中,flatten默认就是flattenMap:这一种操作。

 

1

2

3

4

5

6

7

8

9

10

11

12

public func flatten(_ strategy: FlattenStrategy) -> Signal {

    switch strategy {

    case .merge:

        return self.merge()

 

    case .concat:

        return self.concat()

 

    case .latest:

        return self.switchToLatest()

    }

}

而在ReactiveCocoa v3.x,v4.x,v5.x中,flatten的操作是可以选择3种操作选择的。merge,concat,switchToLatest。

3. flatten:

flatten:操作也必须是对高阶信号进行操作,如果信号里面不是信号,即不是高阶信号,那么就会崩溃。

flatten:的实现比较复杂,一步步的来分析:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

- (RACSignal *)flatten:(NSUInteger)maxConcurrent {

    return [[RACSignal createSignal:^(id subscriber) {

        RACCompoundDisposable *compoundDisposable = [[RACCompoundDisposable alloc] init];

        NSMutableArray *activeDisposables = [[NSMutableArray alloc] initWithCapacity:maxConcurrent];

        NSMutableArray *queuedSignals = [NSMutableArray array];

 

        __block BOOL selfCompleted = NO;

        __block void (^subscribeToSignal)(RACSignal *);

        __weak __block void (^recur)(RACSignal *);

        recur = subscribeToSignal = ^(RACSignal *signal) { // 暂时省略};

 

        void (^completeIfAllowed)(void) = ^{ // 暂时省略};

 

        [compoundDisposable addDisposable:[self subscribeNext:^(RACSignal *signal) {

            if (signal == nil) return;

 

            NSCAssert([signal isKindOfClass:RACSignal.class], @"Expected a RACSignal, got %@", signal);

 

            @synchronized (subscriber) {

                if (maxConcurrent > 0 && activeDisposables.count >= maxConcurrent) {

                    [queuedSignals addObject:signal];

                    return;

                }

            }

 

            subscribeToSignal(signal);

        } error:^(NSError *error) {

            [subscriber sendError:error];

        } completed:^{

            @synchronized (subscriber) {

                selfCompleted = YES;

                completeIfAllowed();

            }

        }]];

 

        return compoundDisposable;

    }] setNameWithFormat:@"[%@] -flatten: %lu", self.name, (unsigned long)maxConcurrent];

}

先来解释一些变量,数组的作用

activeDisposables里面装的是当前正在订阅的订阅者们的disposables信号。

queuedSignals里面装的是被暂时缓存起来的信号,它们等待被订阅。

selfCompleted表示高阶信号是否Completed。

subscribeToSignal闭包的作用是订阅所给的信号。这个闭包的入参参数就是一个信号,在闭包内部订阅这个信号,并进行一些操作。

recur是对subscribeToSignal闭包的一个弱引用,防止strong-weak循环引用,在下面会分析subscribeToSignal闭包,就会明白为什么recur要用weak修饰了。

completeIfAllowed的作用是在所有信号都发送完毕的时候,通知订阅者,给订阅者发送completed。

入参maxConcurrent的意思是最大可容纳同时被订阅的信号个数。

再来详细分析一下具体订阅的过程。

flatten:的内部,订阅高阶信号发出来的信号,这部分的代码比较简单:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

    [self subscribeNext:^(RACSignal *signal) {

        if (signal == nil) return;

 

        NSCAssert([signal isKindOfClass:RACSignal.class], @"Expected a RACSignal, got %@", signal);

 

        @synchronized (subscriber) {

            // 1

            if (maxConcurrent > 0 && activeDisposables.count >= maxConcurrent) {

                [queuedSignals addObject:signal];

                return;

            }

        }

        // 2

        subscribeToSignal(signal);

    } error:^(NSError *error) {

        [subscriber sendError:error];

    } completed:^{

        @synchronized (subscriber) {

            selfCompleted = YES;

            // 3

            completeIfAllowed();

        }

    }]];

 

  1. 如果当前最大可容纳信号的个数 > 0 ,且,activeDisposables数组里面已经装满到最大可容纳信号的个数,不能再装新的信号了。那么就把当前的信号缓存到queuedSignals数组中。
  2. 直到activeDisposables数组里面有空的位子可以加入新的信号,那么就调用subscribeToSignal( )闭包,开始订阅这个新的信号。
  3. 最后完成的时候标记变量selfCompleted为YES,并且调用completeIfAllowed( )闭包。

 

 

1

2

3

4

5

6

void (^completeIfAllowed)(void) = ^{

    if (selfCompleted && activeDisposables.count == 0) {

        [subscriber sendCompleted];

        subscribeToSignal = nil;

    }

};

当selfCompleted = YES 并且activeDisposables数组里面的信号都发送完毕,没有可以发送的信号了,即activeDisposables.count = 0,那么就给订阅者sendCompleted。这里值得一提的是,还需要把subscribeToSignal手动置为nil。因为在subscribeToSignal闭包中强引用了completeIfAllowed闭包,防止completeIfAllowed闭包被提早的销毁掉了。所以在completeIfAllowed闭包执行完毕的时候,需要再把subscribeToSignal闭包置为nil。

那么接下来需要看的重点就是subscribeToSignal( )闭包。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

    recur = subscribeToSignal = ^(RACSignal *signal) {

        RACSerialDisposable *serialDisposable = [[RACSerialDisposable alloc] init];

        // 1

        @synchronized (subscriber) {

            [compoundDisposable addDisposable:serialDisposable];

            [activeDisposables addObject:serialDisposable];

        }

 

        serialDisposable.disposable = [signal subscribeNext:^(id x) {

            [subscriber sendNext:x];

        } error:^(NSError *error) {

            [subscriber sendError:error];

        } completed:^{

            // 2

            __strong void (^subscribeToSignal)(RACSignal *) = recur;

            RACSignal *nextSignal;

            // 3

            @synchronized (subscriber) {

                [compoundDisposable removeDisposable:serialDisposable];

                [activeDisposables removeObjectIdenticalTo:serialDisposable];

                // 4

                if (queuedSignals.count == 0) {

                    completeIfAllowed();

                    return;

                }

                // 5

                nextSignal = queuedSignals[0];

                [queuedSignals removeObjectAtIndex:0];

            }

            // 6

            subscribeToSignal(nextSignal);

        }];

    };

 

  1. activeDisposables先添加当前高阶信号发出来的信号的Disposable( 也就是入参信号的Disposable)
  2. 这里会对recur进行__strong,因为下面第6步会用到subscribeToSignal( )闭包,同样也是为了防止出现循环引用。
  3. 订阅入参信号,给订阅者发送信号。当发送完毕后,activeDisposables中移除它对应的Disposable。
  4. 如果当前缓存的queuedSignals数组里面没有缓存的信号,那么就调用completeIfAllowed( )闭包。
  5. 如果当前缓存的queuedSignals数组里面有缓存的信号,那么就取出第0个信号,并在queuedSignals数组移除它。
  6. 把第4步取出的信号继续订阅,继续调用subscribeToSignal( )闭包。

总结一下:高阶信号每发送一个信号值,判断activeDisposables数组装的个数是否已经超过了maxConcurrent。如果装不下了就缓存进queuedSignals数组中。如果还可以装的下就开始调用subscribeToSignal( )闭包,订阅当前信号。

每发送完一个信号就判断缓存数组queuedSignals的个数,如果缓存数组里面已经没有信号了,那么就结束原来高阶信号的发送。如果缓存数组里面还有信号就继续订阅。如此循环,直到原高阶信号所有的信号都发送完毕。

整个flatten:的执行流程都分析清楚了,最后,关于入参maxConcurrent进行更进一步的解读。

回看上面flatten:的实现中有这样一句话:

 

1

if (maxConcurrent > 0 && activeDisposables.count >= maxConcurrent)

那么maxConcurrent的值域就是最终决定flatten:表现行为。

如果maxConcurrent

 

1

NSMutableArray *activeDisposables = [[NSMutableArray alloc] initWithCapacity:maxConcurrent];

activeDisposables在初始化的时候会初始化一个大小为maxConcurrent的NSMutableArray。如果maxConcurrent

如果maxConcurrent = 0,会发生什么?那么flatten:就退化成flatten了。

如果maxConcurrent = 1,会发生什么?那么flatten:就退化成concat了。

如果maxConcurrent > 1,会发生什么?由于至今还没有遇到能用到maxConcurrent > 1的需求情况,所以这里暂时不展示图解了。maxConcurrent > 1之后,flatten的行为还依照高阶信号的个数和maxConcurrent的关系。如果高阶信号的个数maxConcurrent的值,那么多的信号就会进入queuedSignals缓存数组。

4. concat

这里的concat实现是在RACSignal里面定义的。

 

1

2

3

- (RACSignal *)concat {

    return [[self flatten:1] setNameWithFormat:@"[%@] -concat", self.name];

}

一看源码就知道了,concat其实就是flatten:1。

当然在RACSignal中定义了concat:方法,这个方法在之前的文章已经分析过了,这里回顾对比一下:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

- (RACSignal *)concat:(RACSignal *)signal {

    return [[RACSignal createSignal:^(id subscriber) {

        RACSerialDisposable *serialDisposable = [[RACSerialDisposable alloc] init];

 

        RACDisposable *sourceDisposable = [self subscribeNext:^(id x) {

            [subscriber sendNext:x];

        } error:^(NSError *error) {

            [subscriber sendError:error];

        } completed:^{

            RACDisposable *concattedDisposable = [signal subscribe:subscriber];

            serialDisposable.disposable = concattedDisposable;

        }];

 

        serialDisposable.disposable = sourceDisposable;

        return serialDisposable;

    }] setNameWithFormat:@"[%@] -concat: %@", self.name, signal];

}

 

经过对比可以发现,虽然最终变换出来的结果类似,但是针对的信号的对象是不同的,concat是针对高阶信号进行降阶操作。concat:是把两个信号连接起来的操作。如果把高阶信号按照时间轴,从左往右,依次把每个信号都concat:连接起来,那么结果就是concat。

5. switchToLatest

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

- (RACSignal *)switchToLatest {

    return [[RACSignal createSignal:^(id subscriber) {

        RACMulticastConnection *connection = [self publish];

 

        RACDisposable *subscriptionDisposable = [[connection.signal

                                                  flattenMap:^(RACSignal *x) {

                                                      NSCAssert(x == nil || [x isKindOfClass:RACSignal.class], @"-switchToLatest requires that the source signal (%@) send signals. Instead we got: %@", self, x);

                                                      return [x takeUntil:[connection.signal concat:[RACSignal never]]];

                                                  }]

                                                 subscribe:subscriber];

 

        RACDisposable *connectionDisposable = [connection connect];

        return [RACDisposable disposableWithBlock:^{

            [subscriptionDisposable dispose];

            [connectionDisposable dispose];

        }];

    }] setNameWithFormat:@"[%@] -switchToLatest", self.name];

}

switchToLatest这个操作只能用在高阶信号上,如果原信号里面有不是信号的值,那么就会崩溃,崩溃信息如下:

 

1

***** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-switchToLatest requires that the source signal ( name: ) send signals.

在switchToLatest操作中,先把原信号转换成热信号,connection.signal就是RACSubject类型的。对RACSubject进行flattenMap:变换。在flattenMap:变换中,connection.signal会先concat:一个never信号。这里concat:一个never信号的原因是为了内部的信号过早的结束而导致订阅者收到complete信号。

flattenMap:变换中x也是一个信号,对x进行takeUntil:变换,效果就是下一个信号到来之前,x会一直发送信号,一旦下一个信号到来,x就会被取消订阅,开始订阅新的信号。

一个高阶信号经过switchToLatest降阶操作之后,能得到上图中的信号。

6. switch: cases: default:

switch: cases: default:源码实现如下:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

+ (RACSignal *)switch:(RACSignal *)signal cases:(NSDictionary *)cases default:(RACSignal *)defaultSignal {

    NSCParameterAssert(signal != nil);

    NSCParameterAssert(cases != nil);

 

    for (id key in cases) {

        id value __attribute__((unused)) = cases[key];

        NSCAssert([value isKindOfClass:RACSignal.class], @"Expected all cases to be RACSignals, %@ isn't", value);

    }

 

    NSDictionary *copy = [cases copy];

 

    return [[[signal

              map:^(id key) {

                  if (key == nil) key = RACTupleNil.tupleNil;

 

                  RACSignal *signal = copy[key] ?: defaultSignal;

                  if (signal == nil) {

                      NSString *description = [NSString stringWithFormat:NSLocalizedString(@"No matching signal found for value %@", @""), key];

                      return [RACSignal error:[NSError errorWithDomain:RACSignalErrorDomain code:RACSignalErrorNoMatchingCase userInfo:@{ NSLocalizedDescriptionKey: description }]];

                  }

 

                  return signal;

              }]

             switchToLatest]

            setNameWithFormat:@"+switch: %@ cases: %@ default: %@", signal, cases, defaultSignal];

}

实现中有3个断言,全部都是针对入参的要求。入参signal信号和cases字典都不能是nil。其次,cases字典里面所有key对应的value必须是RACSignal类型的。注意,defaultSignal是可以为nil的。

接下来的实现比较简单,对入参传进来的signal信号进行map变换,这里的变换是升阶的变换。

signal每次发送出来的一个值,就把这个值当做key值去cases字典里面去查找对应的value。当然value对应的是一个信号。如果value对应的信号不为空,就把signal发送出来的这个值map成字典里面对应的信号。如果value对应为空,那么就把原signal发出来的值map成defaultSignal信号。

如果经过转换之后,得到的信号为nil,就会返回一个error信号。如果得到的信号不为nil,那么原信号完全转换完成就会变成一个高阶信号,这个高阶信号里面装的都是信号。最后再对这个高阶信号执行switchToLatest转换。

7. if: then: else:

if: then: else:源码实现如下:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

+ (RACSignal *)if:(RACSignal *)boolSignal then:(RACSignal *)trueSignal else:(RACSignal *)falseSignal {

    NSCParameterAssert(boolSignal != nil);

    NSCParameterAssert(trueSignal != nil);

    NSCParameterAssert(falseSignal != nil);

 

    return [[[boolSignal

              map:^(NSNumber *value) {

                  NSCAssert([value isKindOfClass:NSNumber.class], @"Expected %@ to send BOOLs, not %@", boolSignal, value);

 

                  return (value.boolValue ? trueSignal : falseSignal);

              }]

             switchToLatest]

            setNameWithFormat:@"+if: %@ then: %@ else: %@", boolSignal, trueSignal, falseSignal];

}

入参boolSignal,trueSignal,falseSignal三个信号都不能为nil。

boolSignal里面都必须装的是NSNumber类型的值。

针对boolSignal进行map升阶操作,boolSignal信号里面的值如果是YES,那么就转换成trueSignal信号,如果为NO,就转换成falseSignal。升阶转换完成之后,boolSignal就是一个高阶信号,然后再进行switchToLatest操作。

8. catch:

catch:的实现如下:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

- (RACSignal *)catch:(RACSignal * (^)(NSError *error))catchBlock {

    NSCParameterAssert(catchBlock != NULL);

 

    return [[RACSignal createSignal:^(id subscriber) {

        RACSerialDisposable *catchDisposable = [[RACSerialDisposable alloc] init];

 

        RACDisposable *subscriptionDisposable = [self subscribeNext:^(id x) {

            [subscriber sendNext:x];

        } error:^(NSError *error) {

            RACSignal *signal = catchBlock(error);

            NSCAssert(signal != nil, @"Expected non-nil signal from catch block on %@", self);

            catchDisposable.disposable = [signal subscribe:subscriber];

        } completed:^{

            [subscriber sendCompleted];

        }];

 

        return [RACDisposable disposableWithBlock:^{

            [catchDisposable dispose];

            [subscriptionDisposable dispose];

        }];

    }] setNameWithFormat:@"[%@] -catch:", self.name];

}

当对原信号进行订阅的时候,如果出现了错误,会去执行catchBlock( )闭包,入参为刚刚产生的error。catchBlock( )闭包产生的是一个新的RACSignal,并再次用订阅者订阅该信号。

这里之所以说是高阶操作,是因为这里原信号发生错误之后,错误会升阶成一个信号。

9. catchTo:

catchTo:的实现如下:

 

1

2

3

4

5

- (RACSignal *)catchTo:(RACSignal *)signal {

    return [[self catch:^(NSError *error) {

        return signal;

    }] setNameWithFormat:@"[%@] -catchTo: %@", self.name, signal];

}

catchTo:的实现就是调用catch:方法,只不过原来catch:方法里面的catchBlock( )闭包,永远都只返回catchTo:的入参,signal信号。

10. try:

 

 

1

2

3

4

5

6

7

8

9

- (RACSignal *)try:(BOOL (^)(id value, NSError **errorPtr))tryBlock {

    NSCParameterAssert(tryBlock != NULL);

 

    return [[self flattenMap:^(id value) {

        NSError *error = nil;

        BOOL passed = tryBlock(value, &error);

        return (passed ? [RACSignal return:value] : [RACSignal error:error]);

    }] setNameWithFormat:@"[%@] -try:", self.name];

}

try:也是一个高阶操作。对原信号进行flattenMap变换,对信号发出来的每个值都调用一遍tryBlock( )闭包,如果这个闭包的返回值是YES,那么就返回[RACSignal return:value],如果闭包的返回值是NO,那么就返回error。原信号中如果都是值,那么经过try:操作之后,每个值都会变成RACSignal,于是原信号也就变成了高阶信号了。

11. tryMap:

 

 

1

2

3

4

5

6

7

8

9

- (RACSignal *)tryMap:(id (^)(id value, NSError **errorPtr))mapBlock {

    NSCParameterAssert(mapBlock != NULL);

 

    return [[self flattenMap:^(id value) {

        NSError *error = nil;

        id mappedValue = mapBlock(value, &error);

        return (mappedValue == nil ? [RACSignal error:error] : [RACSignal return:mappedValue]);

    }] setNameWithFormat:@"[%@] -tryMap:", self.name];

}

tryMap:的实现和try:的实现基本一致,唯一不同的就是入参闭包的返回值不同。在tryMap:中调用mapBlock( )闭包,返回是一个对象,如果这个对象不为nil,就返回[RACSignal return:mappedValue]。如果返回的对象是nil,那么就变换成error信号。

12. timeout: onScheduler:

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

- (RACSignal *)timeout:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler {

    NSCParameterAssert(scheduler != nil);

    NSCParameterAssert(scheduler != RACScheduler.immediateScheduler);

 

    return [[RACSignal createSignal:^(id subscriber) {

        RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];

 

        RACDisposable *timeoutDisposable = [scheduler afterDelay:interval schedule:^{

            [disposable dispose];

            [subscriber sendError:[NSError errorWithDomain:RACSignalErrorDomain code:RACSignalErrorTimedOut userInfo:nil]];

        }];

 

        [disposable addDisposable:timeoutDisposable];

 

        RACDisposable *subscriptionDisposable = [self subscribeNext:^(id x) {

            [subscriber sendNext:x];

        } error:^(NSError *error) {

            [disposable dispose];

            [subscriber sendError:error];

        } completed:^{

            [disposable dispose];

            [subscriber sendCompleted];

        }];

 

        [disposable addDisposable:subscriptionDisposable];

        return disposable;

    }] setNameWithFormat:@"[%@] -timeout: %f onScheduler: %@", self.name, (double)interval, scheduler];

}

timeout: onScheduler:的实现很简单,它比正常的信号订阅多了一个timeoutDisposable操作。它在信号订阅的内部开启了一个scheduler,经过interval的时间之后,就会停止订阅原信号,并对订阅者sendError。

这个操作的表意和方法名完全一致,经过interval的时间之后,就算timeout,那么就停止订阅原信号,并sendError。

总结一下ReactiveCocoa v2.5中高阶信号的升阶 / 降阶操作:

升阶操作

  1. map( 把值map成一个信号)
  2. [RACSignal return:signal]

降阶操作

  1. flatten(等效于flatten:0,+merge:)
  2. concat(等效于flatten:1)
  3. flatten:1
  4. switchToLatest
  5. flattenMap:

这5种操作能将高阶信号变为低阶信号,但是最终降阶之后的效果就只有3种:switchToLatest,flatten,concat。具体的图示见上面的分析。

二. 同步操作

在ReactiveCocoa中还包含一些同步的操作,这些操作一般我们很少使用,除非真的很确定这样做了之后不会有什么问题,否则胡乱使用会导致线程死锁等一些严重的问题。

1. firstOrDefault: success: error:

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

- (id)firstOrDefault:(id)defaultValue success:(BOOL *)success error:(NSError **)error {

    NSCondition *condition = [[NSCondition alloc] init];

    condition.name = [NSString stringWithFormat:@"[%@] -firstOrDefault: %@ success:error:", self.name, defaultValue];

 

    __block id value = defaultValue;

    __block BOOL done = NO;

 

    // Ensures that we don't pass values across thread boundaries by reference.

    __block NSError *localError;

    __block BOOL localSuccess;

 

    [[self take:1] subscribeNext:^(id x) {

        // 加锁

        [condition lock];

 

        value = x;

        localSuccess = YES;

 

        done = YES;

        [condition broadcast];

        // 解锁

        [condition unlock];

    } error:^(NSError *e) {

        // 加锁

        [condition lock];

 

        if (!done) {

            localSuccess = NO;

            localError = e;

 

            done = YES;

            [condition broadcast];

        }

        // 解锁

        [condition unlock];

    } completed:^{

        // 加锁

        [condition lock];

 

        localSuccess = YES;

 

        done = YES;

        [condition broadcast];

        // 解锁

        [condition unlock];

    }];

    // 加锁

    [condition lock];

    while (!done) {

        [condition wait];

    }

 

    if (success != NULL) *success = localSuccess;

    if (error != NULL) *error = localError;

    // 解锁

    [condition unlock];

    return value;

}

从源码上看,firstOrDefault: success: error:这种同步的方法很容易导致线程死锁。它在subscribeNext,error,completed的闭包里面都调用condition锁先lock再unlock。如果一个信号发送值过来,都没有执行subscribeNext,error,completed这3个操作里面的任意一个,那么就会执行[condition wait],等待。

由于对原信号进行了take:1操作,所以只会对第一个值进行操作。执行完subscribeNext,error,completed这3个操作里面的任意一个,又会加一次锁,对外部传进来的入参success和error进行赋值,已便外部可以拿到里面的状态。最终返回信号是原信号中第一个next里面的值,如果原信号第一个值没有,比如直接error或者completed,那么返回的是defaultValue。

done为YES表示已经成功执行了subscribeNext,error,completed这3个操作里面的任意一个。反之为NO。

localSuccess为YES表示成功发送值或者成功发送完了原信号的所有值,期间没有发生错误。

condition的broadcast操作是唤醒其他线程的操作,相当于操作系统里面互斥信号量的signal操作。

入参defaultValue是给内部变量value的一个初始值。当原信号发送出一个值之后,value的值时刻都会与原信号的值保持一致。

success和error是外部变量的地址,从外面可以监听到里面的状态。在函数内部赋值,在函数外面拿到它们的值。

2. firstOrDefault:

 

 

1

2

3

- (id)firstOrDefault:(id)defaultValue {

    return [self firstOrDefault:defaultValue success:NULL error:NULL];

}

firstOrDefault:的实现就是调用了firstOrDefault: success: error:方法。只不过不需要传success和error,不关心内部的状态。最终返回信号是原信号中第一个next里面的值,如果原信号第一个值没有,比如直接error或者completed,那么返回的是defaultValue。

3. first

 

 

1

2

3

- (id)first {

    return [self firstOrDefault:nil];

}

first方法就更加省略,连defaultValue也不传。最终返回信号是原信号中第一个next里面的值,如果原信号第一个值没有,比如直接error或者completed,那么返回的是nil。

4. waitUntilCompleted:

 

 

1

2

3

4

5

6

7

8

9

10

- (BOOL)waitUntilCompleted:(NSError **)error {

    BOOL success = NO;

 

    [[[self

       ignoreValues]

      setNameWithFormat:@"[%@] -waitUntilCompleted:", self.name]

     firstOrDefault:nil success:&success error:error];

 

    return success;

}

waitUntilCompleted:里面还是调用firstOrDefault: success: error:方法。返回值是success。只要原信号正常的发送完信号,success应该为YES,但是如果发送过程中出现了error,success就为NO。success作为返回值,外部就可以监听到是否发送成功。

虽然这个方法可以监听到发送结束的状态,但是也尽量不要使用,因为它的实现调用了firstOrDefault: success: error:方法,这个方法里面有大量的锁的操作,一不留神就会导致死锁。

5. toArray

 

 

1

2

3

- (NSArray *)toArray {

    return [[[self collect] first] copy];

}

经过collect之后,原信号所有的值都会被加到一个数组里面,取出信号的第一个值就是一个数组。所以执行完first之后第一个值就是原信号所有值的数组。

三. 副作用操作

ReactiveCocoa v2.5中还为我们提供了一些可以进行副作用操作的函数。

1. doNext:

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

- (RACSignal *)doNext:(void (^)(id x))block {

    NSCParameterAssert(block != NULL);

 

    return [[RACSignal createSignal:^(id subscriber) {

        return [self subscribeNext:^(id x) {

            block(x);

            [subscriber sendNext:x];

        } error:^(NSError *error) {

            [subscriber sendError:error];

        } completed:^{

            [subscriber sendCompleted];

        }];

    }] setNameWithFormat:@"[%@] -doNext:", self.name];

}

doNext:能让我们在原信号sendNext之前,能执行一个block闭包,在这个闭包中我们可以执行我们想要执行的副作用操作。

2. doError:

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

- (RACSignal *)doError:(void (^)(NSError *error))block {

    NSCParameterAssert(block != NULL);

 

    return [[RACSignal createSignal:^(id subscriber) {

        return [self subscribeNext:^(id x) {

            [subscriber sendNext:x];

        } error:^(NSError *error) {

            block(error);

            [subscriber sendError:error];

        } completed:^{

            [subscriber sendCompleted];

        }];

    }] setNameWithFormat:@"[%@] -doError:", self.name];

}

doError:能让我们在原信号sendError之前,能执行一个block闭包,在这个闭包中我们可以执行我们想要执行的副作用操作。

3. doCompleted:

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

- (RACSignal *)doCompleted:(void (^)(void))block {

    NSCParameterAssert(block != NULL);

 

    return [[RACSignal createSignal:^(id subscriber) {

        return [self subscribeNext:^(id x) {

            [subscriber sendNext:x];

        } error:^(NSError *error) {

            [subscriber sendError:error];

        } completed:^{

            block();

            [subscriber sendCompleted];

        }];

    }] setNameWithFormat:@"[%@] -doCompleted:", self.name];

}

doCompleted:能让我们在原信号sendCompleted之前,能执行一个block闭包,在这个闭包中我们可以执行我们想要执行的副作用操作。

4. initially:

 

 

1

2

3

4

5

6

7

8

- (RACSignal *)initially:(void (^)(void))block {

    NSCParameterAssert(block != NULL);

 

    return [[RACSignal defer:^{

        block();

        return self;

    }] setNameWithFormat:@"[%@] -initially:", self.name];

}

initially:能让我们在原信号发送之前,先调用了defer:操作,在return self之前先执行了一个闭包,在这个闭包中我们可以执行我们想要执行的副作用操作。

5. finally:

 

 

1

2

3

4

5

6

7

8

9

10

11

12

- (RACSignal *)finally:(void (^)(void))block {

    NSCParameterAssert(block != NULL);

 

    return [[[self

              doError:^(NSError *error) {

                  block();

              }]

             doCompleted:^{

                 block();

             }]

            setNameWithFormat:@"[%@] -finally:", self.name];

}

finally:操作调用了doError:和doCompleted:操作,依次在sendError之前,sendCompleted之前,插入一个block( )闭包。这样当信号因为错误而要终止取消订阅,或者,发送结束之前,都能执行一段我们想要执行的副作用操作。

四. 多线程操作

在RACSignal里面有3个关于多线程的操作。

1. deliverOn:

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

- (RACSignal *)deliverOn:(RACScheduler *)scheduler {

    return [[RACSignal createSignal:^(id subscriber) {

        return [self subscribeNext:^(id x) {

            [scheduler schedule:^{

                [subscriber sendNext:x];

            }];

        } error:^(NSError *error) {

            [scheduler schedule:^{

                [subscriber sendError:error];

            }];

        } completed:^{

            [scheduler schedule:^{

                [subscriber sendCompleted];

            }];

        }];

    }] setNameWithFormat:@"[%@] -deliverOn: %@", self.name, scheduler];

}

deliverOn:的入参是一个scheduler,当原信号subscribeNext,sendError,sendCompleted的时候,都去调用scheduler的schedule方法。

 

1

2

3

4

5

6

7

8

- (RACDisposable *)schedule:(void (^)(void))block {

    NSCParameterAssert(block != NULL);

 

    if (RACScheduler.currentScheduler == nil) return [self.backgroundScheduler schedule:block];

 

    block();

    return nil;

}

在schedule的方法里面会判断当前currentScheduler是否为nil,如果是nil就调用backgroundScheduler去执行block( )闭包,如果不为nil,当前currentScheduler直接执行block( )闭包。

 

1

2

3

4

5

6

7

+ (instancetype)currentScheduler {

    RACScheduler *scheduler = NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey];

    if (scheduler != nil) return scheduler;

    if ([self.class isOnMainThread]) return RACScheduler.mainThreadScheduler;

 

    return nil;

}

判断currentScheduler是否存在,看两点,一是当前线程的字典里面,是否存在RACSchedulerCurrentSchedulerKey( @”RACSchedulerCurrentSchedulerKey” ),如果存在对应的value,返回scheduler,二是看当前的类是不是在主线程,如果在主线程,返回mainThreadScheduler。如果两个条件都不存在,那么当前currentScheduler就不存在,返回nil。

deliverOn:操作的特点是原信号发送sendNext,sendError,sendCompleted所在线程是确定的。

2. subscribeOn:

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

- (RACSignal *)subscribeOn:(RACScheduler *)scheduler {

    return [[RACSignal createSignal:^(id subscriber) {

        RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];

 

        RACDisposable *schedulingDisposable = [scheduler schedule:^{

            RACDisposable *subscriptionDisposable = [self subscribe:subscriber];

 

            [disposable addDisposable:subscriptionDisposable];

        }];

 

        [disposable addDisposable:schedulingDisposable];

        return disposable;

    }] setNameWithFormat:@"[%@] -subscribeOn: %@", self.name, scheduler];

}

subscribeOn:操作就是在传入的scheduler的闭包内部订阅原信号的。它与deliverOn:操作就不同:

subscribeOn:操作能够保证didSubscribe block( )闭包在入参scheduler中执行,但是不能保证原信号subscribeNext,sendError,sendCompleted在哪个scheduler中执行。

deliverOn:与subscribeOn:正好反过来,能保证原信号subscribeNext,sendError,sendCompleted在哪个scheduler中执行,但是不能保证didSubscribe block( )闭包在哪个scheduler中执行。

3. deliverOnMainThread

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

- (RACSignal *)deliverOnMainThread {

    return [[RACSignal createSignal:^(id subscriber) {

        __block volatile int32_t queueLength = 0;

 

        void (^performOnMainThread)(dispatch_block_t) = ^(dispatch_block_t block) { // 暂时省略};

 

        return [self subscribeNext:^(id x) {

            performOnMainThread(^{

                [subscriber sendNext:x];

            });

        } error:^(NSError *error) {

            performOnMainThread(^{

                [subscriber sendError:error];

            });

        } completed:^{

            performOnMainThread(^{

                [subscriber sendCompleted];

            });

        }];

    }] setNameWithFormat:@"[%@] -deliverOnMainThread", self.name];

}

对比deliverOn:的源码实现,发现两者比较相似,只不过这里deliverOnMainThread把sendNext,sendError,sendCompleted都包在了performOnMainThread闭包中执行。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

        __block volatile int32_t queueLength = 0;

 

        void (^performOnMainThread)(dispatch_block_t) = ^(dispatch_block_t block) {

            int32_t queued = OSAtomicIncrement32(&queueLength);

            if (NSThread.isMainThread && queued == 1) {

                block();

                OSAtomicDecrement32(&queueLength);

            } else {

                dispatch_async(dispatch_get_main_queue(), ^{

                    block();

                    OSAtomicDecrement32(&queueLength);

                });

            }

        };

performOnMainThread闭包内部保证了入参block( )闭包一定是在主线程中执行。

OSAtomicIncrement32 和 OSAtomicDecrement32是原子操作,分别代表+1和-1。下面的if-else判断里面,不管是满足哪一条,最终都还是在主线程中执行block( )闭包。

deliverOnMainThread能保证原信号subscribeNext,sendError,sendCompleted都在主线程MainThread中执行。

五. 其他操作

1. setKeyPath: onObject: nilValue:

setKeyPath: onObject: nilValue: 的源码实现如下:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

- (RACDisposable *)setKeyPath:(NSString *)keyPath onObject:(NSObject *)object nilValue:(id)nilValue {

    NSCParameterAssert(keyPath != nil);

    NSCParameterAssert(object != nil);

 

    keyPath = [keyPath copy];

 

    RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];

 

    __block void * volatile objectPtr = (__bridge void *)object;

 

    RACDisposable *subscriptionDisposable = [self subscribeNext:^(id x) {

        // 1

        __strong NSObject *object __attribute__((objc_precise_lifetime)) = (__bridge __strong id)objectPtr;

        [object setValue:x ?: nilValue forKeyPath:keyPath];

    } error:^(NSError *error) {

        __strong NSObject *object __attribute__((objc_precise_lifetime)) = (__bridge __strong id)objectPtr;

 

        NSCAssert(NO, @"Received error from %@ in binding for key path \"%@\" on %@: %@", self, keyPath, object, error);

        NSLog(@"Received error from %@ in binding for key path \"%@\" on %@: %@", self, keyPath, object, error);

 

        [disposable dispose];

    } completed:^{

        [disposable dispose];

    }];

 

    [disposable addDisposable:subscriptionDisposable];

 

#if DEBUG

    static void *bindingsKey = &bindingsKey;

    NSMutableDictionary *bindings;

 

    @synchronized (object) {

        // 2

        bindings = objc_getAssociatedObject(object, bindingsKey);

        if (bindings == nil) {

            bindings = [NSMutableDictionary dictionary];

            objc_setAssociatedObject(object, bindingsKey, bindings, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

        }

    }

 

    @synchronized (bindings) {

        NSCAssert(bindings[keyPath] == nil, @"Signal %@ is already bound to key path \"%@\" on object %@, adding signal %@ is undefined behavior", [bindings[keyPath] nonretainedObjectValue], keyPath, object, self);

 

        bindings[keyPath] = [NSValue valueWithNonretainedObject:self];

    }

#endif

 

    RACDisposable *clearPointerDisposable = [RACDisposable disposableWithBlock:^{

#if DEBUG

        @synchronized (bindings) {

            // 3

            [bindings removeObjectForKey:keyPath];

        }

#endif

 

        while (YES) {

            void *ptr = objectPtr;

            // 4

            if (OSAtomicCompareAndSwapPtrBarrier(ptr, NULL, &objectPtr)) {

                break;

            }

        }

    }];

 

    [disposable addDisposable:clearPointerDisposable];

 

    [object.rac_deallocDisposable addDisposable:disposable];

 

    RACCompoundDisposable *objectDisposable = object.rac_deallocDisposable;

    return [RACDisposable disposableWithBlock:^{

        [objectDisposable removeDisposable:disposable];

        [disposable dispose];

    }];

}

代码虽然有点长,但是逐行读下来不是很难,需要注意的有4点地方,已经在上述代码里面标明了。接下来一一分析。

1. objc_precise_lifetime的问题。

作者在这里写了一段注释:

Possibly spec, possibly compiler bug, but this __bridge cast does not result in a retain here, effectively an invisible __unsafe_unretained qualifier. Using objc_precise_lifetime gives the __strong reference desired. The explicit use of __strong is strictly defensive.

作者怀疑是编译器的一个bug,即使是显示的调用了__strong,依旧没法保证被强引用了,所以还需要用objc_precise_lifetime来保证强引用。

关于这个问题,笔者查询了一下LLVM的文档,在6.3 precise lifetime semantics这一节中提到了这个问题。

通常上,凡是声明了__strong的变量,都会有很确切的生命周期。ARC会维持这些__strong的变量在其生命周期中被retained。

但是自动存储的局部变量是没有确切的生命周期的。这些变量仅仅只是简单的持有一个强引用,强引用着retain对象的指针类型的值。这些值完全受控于本地控制者的如何优化。所以要想改变这些局部变量的生命周期,是不可能的事情。因为有太多的优化,理论上都会导致局部变量的生命周期减少,但是这些优化非常有用。

但是LLVM为我们提供了一个关键字objc_precise_lifetime,使用这个可以是局部变量的生命周期变成确切的。这个关键字有时候还是非常有用的。甚至更加极端情况,该局部变量都没有被使用,但是它依旧可以保持一个确定的生命周期。

回到源码上来,接着代码会对入参object进行setValue: forKeyPath:

 

1

[object setValue:x ?: nilValue forKeyPath:keyPath];

如何x为nil就返回nilValue传进来的值。

2. AssociatedObject关联对象

如果bindings字典不存在,那么就调用objc_setAssociatedObject对object进行关联对象。参数是OBJC_ASSOCIATION_RETAIN_NONATOMIC。如果bindings字典存在,就用objc_getAssociatedObject取出字典。

在字典里面重新更新绑定key-value值,key就是入参keyPath,value是原信号。

3. 取消订阅原信号的时候

 

 

1

[bindings removeObjectForKey:keyPath];

当信号取消订阅的时候,移除所有的关联值。

3. OSAtomicCompareAndSwapPtrBarrier

这个函数属于OSAtomic原子操作,原型如下:

 

1

OSAtomicCompareAndSwapPtrBarrier(type __oldValue, type __newValue, volatile type *__theValue)

 

Compares a variable against the specified old value. If the two values are equal, this function assigns the specified new value to the variable; otherwise, it does nothing. The comparison and assignment are done as one atomic operation and the function returns a Boolean value indicating whether the swap actually occurred.

这个函数用于比较__oldValue是否与__theValue指针指向的内存位置的值匹配,如果匹配,则将__newValue的值存储到__theValue指向的内存位置。整个函数的返回值就是交换是否成功的BOOL值。

 

1

2

3

4

5

6

    while (YES) {

    void *ptr = objectPtr;

    if (OSAtomicCompareAndSwapPtrBarrier(ptr, NULL, &objectPtr))   {

          break;

    }

  }

在这个while的死循环里面只有当OSAtomicCompareAndSwapPtrBarrier返回值为YES,才能退出整个死循环。返回值为YES就代表&objectPtr被置为了NULL,这样就确保了在线程安全的情况下,不存在野指针的问题了。

2. setKeyPath: onObject:

 

 

1

2

3

- (RACDisposable *)setKeyPath:(NSString *)keyPath onObject:(NSObject *)object {

    return [self setKeyPath:keyPath onObject:object nilValue:nil];

}

setKeyPath: onObject:就是调用setKeyPath: onObject: nilValue:方法,只不过nilValue传递的是nil。

 

在ReactiveCocoa 过程中,除去RACSignal和RACSubject这些信号类以外,有些时候我们可能还需要封装一些固定的操作集合。这些操作集合都是固定的,每次只要一触发就会执行事先定义好的一个过程。在iOS开发过程中,按钮的点击事件就可能有这种需求。那么RACCommand就可以实现这种需求。

当然除了封装一个操作集合以外,RACCommand还能集中处理错误等等功能。今天就来从底层来看看RACCommand是如何实现的。

目录

  • 1.RACCommand的定义
  • 2.initWithEnabled: signalBlock: 底层实现分析
  • 3.execute:底层实现分析
  • 4.RACCommand的一些Category

一. RACCommand的定义

首先说说RACCommand的作用。
RACCommand 在ReactiveCocoa 中是对一个动作的触发条件以及它产生的触发事件的封装。

  • 触发条件:初始化RACCommand的入参enabledSignal就决定了RACCommand是否能开始执行。入参enabledSignal就是触发条件。举个例子,一个按钮是否能点击,是否能触发点击事情,就由入参enabledSignal决定。
  • 触发事件:初始化RACCommand的另外一个入参(RACSignal * (^)(id input))signalBlock就是对触发事件的封装。RACCommand每次执行都会调用一次signalBlock闭包。

RACCommand最常见的例子就是在注册登录的时候,点击获取验证码的按钮,这个按钮的点击事件和触发条件就可以用RACCommand来封装,触发条件是一个信号,它可以是验证手机号,验证邮箱,验证身份证等一些验证条件产生的enabledSignal。触发事件就是按钮点击之后执行的事件,可以是发送验证码的网络请求。

RACCommand在ReactiveCocoa中算是很特别的一种存在,因为它的实现并不是FRP实现的,是OOP实现的。RACCommand的本质就是一个对象,在这个对象里面封装了4个信号。

关于RACCommand的定义如下:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

@interface RACCommand : NSObject

@property (nonatomic, strong, readonly) RACSignal *executionSignals;

@property (nonatomic, strong, readonly) RACSignal *executing;

@property (nonatomic, strong, readonly) RACSignal *enabled;

@property (nonatomic, strong, readonly) RACSignal *errors;

@property (atomic, assign) BOOL allowsConcurrentExecution;

volatile uint32_t _allowsConcurrentExecution;

 

@property (atomic, copy, readonly) NSArray *activeExecutionSignals;

NSMutableArray *_activeExecutionSignals;

 

@property (nonatomic, strong, readonly) RACSignal *immediateEnabled;

@property (nonatomic, copy, readonly) RACSignal * (^signalBlock)(id input);

@end

RACCommand中4个最重要的信号就是定义开头的那4个信号,executionSignals,executing,enabled,errors。需要注意的是,这4个信号基本都是(并不是完全是)在主线程上执行的

1. RACSignal *executionSignals

executionSignals是一个高阶信号,所以在使用的时候需要进行降阶操作,降价操作在前面分析过了,在ReactiveCocoa v2.5中只支持3种降阶方式,flatten,switchToLatest,concat。降阶的方式就根据需求来选取。

还有选择原则是,如果在不允许Concurrent并发的RACCommand中一般使用switchToLatest。如果在允许Concurrent并发的RACCommand中一般使用flatten。

2. RACSignal *executing

executing这个信号就表示了当前RACCommand是否在执行,信号里面的值都是BOOL类型的。YES表示的是RACCommand正在执行过程中,命名也说明的是正在进行时ing。NO表示的是RACCommand没有被执行或者已经执行结束。

3. RACSignal *enabled

enabled信号就是一个开关,RACCommand是否可用。这个信号除去以下2种情况会返回NO:

  • RACCommand 初始化传入的enabledSignal信号,如果返回NO,那么enabled信号就返回NO。
  • RACCommand开始执行中,allowsConcurrentExecution为NO,那么enabled信号就返回NO。

除去以上2种情况以外,enabled信号基本都是返回YES。

4. RACSignal *errors

errors信号就是RACCommand执行过程中产生的错误信号。这里特别需要注意的是:在对RACCommand进行错误处理的时候,我们不应该使用subscribeError:对RACCommand的executionSignals
进行错误的订阅
,因为executionSignals这个信号是不会发送error事件的,那当RACCommand包裹的信号发送error事件时,我们要怎样去订阅到它呢?应该用subscribeNext:去订阅错误信号

 

1

2

3

[commandSignal.errors subscribeNext:^(NSError *x) {    

    NSLog(@"ERROR! --> %@",x);

}];

 

5. BOOL allowsConcurrentExecution

allowsConcurrentExecution是一个BOOL变量,它是用来表示当前RACCommand是否允许并发执行。默认值是NO。

如果allowsConcurrentExecution为NO,那么RACCommand在执行过程中,enabled信号就一定都返回NO,不允许并发执行。如果allowsConcurrentExecution为YES,允许并发执行。

如果是允许并发执行的话,就会出现多个信号就会出现一起发送值的情况。那么这种情况产生的高阶信号一般可以采取flatten(等效于flatten:0,+merge:)的方式进行降阶。

这个变量在具体实现中是用的volatile原子的操作,在实现中重写了它的get和set方法。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

// 重写 get方法

- (BOOL)allowsConcurrentExecution {

    return _allowsConcurrentExecution != 0;

}

 

// 重写 set方法

- (void)setAllowsConcurrentExecution:(BOOL)allowed {

    [self willChangeValueForKey:@keypath(self.allowsConcurrentExecution)];

 

    if (allowed) {

        OSAtomicOr32Barrier(1, &_allowsConcurrentExecution);

    } else {

        OSAtomicAnd32Barrier(0, &_allowsConcurrentExecution);

    }

 

    [self didChangeValueForKey:@keypath(self.allowsConcurrentExecution)];

}

OSAtomicOr32Barrier是原子运算,它的意义是进行逻辑的“或”运算。通过原子性操作访问被volatile修饰的_allowsConcurrentExecution对象即可保障函数只执行一次。相应的OSAtomicAnd32Barrier也是原子运算,它的意义是进行逻辑的“与”运算。

6. NSArray *activeExecutionSignals

这个NSArray数组里面装了一个个有序排列的,执行中的信号。NSArray的数组是可以被KVO监听的。

 

1

2

3

4

5

- (NSArray *)activeExecutionSignals {

    @synchronized (self) {

        return [_activeExecutionSignals copy];

    }

}

当然内部还有一个NSMutableArray的版本,NSArray数组是它的copy版本,使用它的时候需要加上线程锁,进行线程安全的保护。

在RACCommand内部,是对NSMutableArray数组进行操作的,在这里可变数组里面进行增加和删除的操作。

 

1

2

3

4

5

6

7

8

9

10

- (void)addActiveExecutionSignal:(RACSignal *)signal {

    NSCParameterAssert([signal isKindOfClass:RACSignal.class]);

 

    @synchronized (self) {

        NSIndexSet *indexes = [NSIndexSet indexSetWithIndex:_activeExecutionSignals.count];

        [self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:@keypath(self.activeExecutionSignals)];

        [_activeExecutionSignals addObject:signal];

        [self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:@keypath(self.activeExecutionSignals)];

    }

}

在往数组里面添加数据的时候是满足KVO的,这里对index进行了NSKeyValueChangeInsertion监听。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

- (void)removeActiveExecutionSignal:(RACSignal *)signal {

    NSCParameterAssert([signal isKindOfClass:RACSignal.class]);

 

    @synchronized (self) {

        NSIndexSet *indexes = [_activeExecutionSignals indexesOfObjectsPassingTest:^ BOOL (RACSignal *obj, NSUInteger index, BOOL *stop) {

            return obj == signal;

        }];

 

        if (indexes.count == 0) return;

 

        [self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:@keypath(self.activeExecutionSignals)];

        [_activeExecutionSignals removeObjectsAtIndexes:indexes];

        [self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:@keypath(self.activeExecutionSignals)];

    }

}

在移除数组里面也是依照indexes来进行移除的。注意,增加和删除的操作都必须包在@synchronized (self)中保证线程安全。

 

1

2

3

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {

    return NO;

}

从上面增加和删除的操作中我们可以看见了RAC的作者在手动发送change notification,手动调用willChange: 和 didChange:方法。作者的目的在于防止一些不必要的swizzling可能会影响到增加和删除的操作,所以这里选择的手动发送通知的方式。

美团博客上这篇ReactiveCocoa核心元素与信号流文章里面对activeExecutionSignals的变化引起的一些变化画了一张数据流图:

除去没有影响到enabled信号,activeExecutionSignals的变化会影响到其他三个信号。

7. RACSignal *immediateEnabled

这个信号也是一个enabled信号,但是和之前的enabled信号不同的是,它并不能保证在main thread主线程上,它可以在任意一个线程上。

8. RACSignal * (^signalBlock)(id input)

这个闭包返回值是一个信号,这个闭包是在初始化RACCommand的时候会用到,下面分析源码的时候会出现。

 

1

2

3

- (id)initWithSignalBlock:(RACSignal * (^)(id input))signalBlock;

- (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(id input))signalBlock;

- (RACSignal *)execute:(id)input;

RACCommand 暴露出来的就3个方法,2个初始化方法和1个execute:的方法,接下来就来分析一下这些方法的底层实现。

二. initWithEnabled: signalBlock: 底层实现分析

首先先来看看比较短的那个初始化方法。

 

1

2

3

- (id)initWithSignalBlock:(RACSignal * (^)(id input))signalBlock {

    return [self initWithEnabled:nil signalBlock:signalBlock];

}

initWithSignalBlock:方法实际就是调用了initWithEnabled: signalBlock:方法。

 

1

2

3

- (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(id input))signalBlock {

 

}

initWithSignalBlock:方法相当于第一个参数传的是nil的initWithEnabled: signalBlock:方法。第一个参数是enabledSignal,第二个参数是signalBlock的闭包。enabledSignal如果传的是nil,那么就相当于是传进了[RACSignal return:@YES]。

接下来详细分析一下initWithEnabled: signalBlock:方法的实现。

这个方法的实现非常长,需要分段来分析。RACCommand的初始化就是对自己的4个信号,executionSignals,executing,enabled,errors的初始化。

1. executionSignals信号的初始化

 

 

1

2

3

4

5

6

7

8

9

10

11

RACSignal *newActiveExecutionSignals = [[[[[self rac_valuesAndChangesForKeyPath:@keypath(self.activeExecutionSignals) options:NSKeyValueObservingOptionNew observer:nil]

 

    reduceEach:^(id _, NSDictionary *change) {

    NSArray *signals = change[NSKeyValueChangeNewKey];

    if (signals == nil) return [RACSignal empty];

 

    return [signals.rac_sequence signalWithScheduler:RACScheduler.immediateScheduler];

    }]

   concat]

   publish]

   autoconnect];

通过rac_valuesAndChangesForKeyPath: options: observer: 方法监听self.activeExecutionSignals数组里面是否有增加新的信号。rac_valuesAndChangesForKeyPath: options: observer: 方法的返回时是一个RACTuple,它的定义是这样的:RACTuplePack(value, change)。

只要每次数组里面加入了新的信号,那么rac_valuesAndChangesForKeyPath: options: observer: 方法就会把新加的值和change字典包装成RACTuple返回。再对这个信号进行一次reduceEach:操作。

举个例子,change字典可能是如下的样子:

 

1

2

3

4

5

6

7

{

    indexes = "[number of indexes: 1 (in 1 ranges), indexes: (0)]";

    kind = 2;

    new =     (

        " name: "

    );

}

取出change[NSKeyValueChangeNewKey]就能取出每次变化新增的信号数组,然后把这个数组通过signalWithScheduler:转换成信号。

把原信号中每个值是里面装满RACTuple的信号通过变换,变换成了装满RACSingnal的三阶信号,通过concat进行降阶操作,降阶成了二阶信号。最后通过publish和autoconnect操作,把冷信号转换成热信号。

newActiveExecutionSignals最终是一个二阶热信号。

接下来再看看executionSignals是如何变换而来的。

 

1

2

3

4

5

6

_executionSignals = [[[newActiveExecutionSignals

                       map:^(RACSignal *signal) {

                           return [signal catchTo:[RACSignal empty]];

                       }]

                      deliverOn:RACScheduler.mainThreadScheduler]

                     setNameWithFormat:@"%@ -executionSignals", self];

executionSignals把newActiveExecutionSignals中错误信号都换成空信号。经过map变换之后,executionSignals是newActiveExecutionSignals的无错误信号的版本。由于map只是变换并没有降阶,所以executionSignals还是一个二阶的高阶冷信号。

注意最后加上了deliverOn,executionSignals信号每个值都是在主线程中发送的。

2. errors信号的初始化

在RACCommand中会搜集其所有的error信号,都装进自己的errors的信号中。这也是RACCommand的特点之一,能把错误统一处理。

 

1

2

3

4

5

6

7

8

9

RACMulticastConnection *errorsConnection = [[[newActiveExecutionSignals

                                              flattenMap:^(RACSignal *signal) {

                                                  return [[signal ignoreValues]

                                                          catch:^(NSError *error) {

                                                              return [RACSignal return:error];

                                                          }];

                                              }]

                                             deliverOn:RACScheduler.mainThreadScheduler]

                                             publish];

从上面分析中,我们知道,newActiveExecutionSignals最终是一个二阶热信号。这里在errorsConnection的变换中,我们对这个二阶的热信号进行flattenMap:降阶操作,只留下所有的错误信号,最后把所有的错误信号都装在一个低阶的信号中,这个信号中每个值都是一个error。同样,变换中也追加了deliverOn:操作,回到主线程中去操作。最后把这个冷信号转换成热信号,但是注意,还没有connect。

 

1

2

_errors = [errorsConnection.signal setNameWithFormat:@"%@ -errors", self];

[errorsConnection connect];

假设某个订阅者在RACCommand中的信号已经开始执行之后才订阅的,如果错误信号是一个冷信号,那么订阅之前的错误就接收不到了。所以错误应该是一个热信号,不管什么时候订阅都可以接收到所有的错误。

error信号就是热信号errorsConnection传出来的一个热信号。error信号每个值都是在主线程上发送的。

3. executing信号的初始化

executing这个信号表示了当前RACCommand是否在执行,信号里面的值都是BOOL类型的。那么如何拿到这样一个BOOL信号呢?

 

1

2

3

RACSignal *immediateExecuting = [RACObserve(self, activeExecutionSignals) map:^(NSArray *activeSignals) {

    return @(activeSignals.count > 0);

}];

由于self.activeExecutionSignals是可以被KVO的,所以每当activeExecutionSignals变化的时候,判断当前数组里面是否还有信号,如果数组里面有值,就代表了当前有在执行中的信号。

 

1

2

3

4

5

6

_executing = [[[[[immediateExecuting

                  deliverOn:RACScheduler.mainThreadScheduler]

                  startWith:@NO]

                  distinctUntilChanged]

                  replayLast]

                  setNameWithFormat:@"%@ -executing", self];

immediateExecuting信号表示当前是否有信号在执行。初始值为NO,一旦immediateExecuting不为NO的时候就会发出信号。最后通过replayLast转换成永远只保存最新的一个值的热信号。

executing信号除去第一个默认值NO,其他的每个值也是在主线程中发送的。

4. enabled信号的初始化

 

 

1

2

3

4

RACSignal *moreExecutionsAllowed = [RACSignal

                                    if:RACObserve(self, allowsConcurrentExecution)

                                    then:[RACSignal return:@YES]

                                    else:[immediateExecuting not]];

先监听self.allowsConcurrentExecution变量是否有变化,allowsConcurrentExecution默认值为NO。如果有变化,allowsConcurrentExecution为YES,就说明允许并发执行,那么就返回YES的RACSignal,allowsConcurrentExecution为NO,就说明不允许并发执行,那么就要看当前是否有正在执行的信号。immediateExecuting就是代表当前是否有在执行的信号,对这个信号取非,就是是否允许执行下一个信号的BOOL值。这就是moreExecutionsAllowed的信号。

 

1

2

3

4

5

6

7

8

if (enabledSignal == nil) {

    enabledSignal = [RACSignal return:@YES];

} else {

    enabledSignal = [[[enabledSignal

                       startWith:@YES]

                       takeUntil:self.rac_willDeallocSignal]

                       replayLast];

}

这里的代码就说明了,如果第一个参数传的是nil,那么就相当于传进来了一个[RACSignal return:@YES]信号。

如果enabledSignal不为nil,就在enabledSignal信号前面插入一个YES的信号,目的是为了防止传入的enabledSignal虽然不为nil,但是里面是没有信号的,比如[RACSignal never],[RACSignal empty],这些信号传进来也相当于是没用的,所以在开头加一个YES的初始值信号。

最后同样通过replayLast操作转换成只保存最新的一个值的热信号。

 

1

2

3

_immediateEnabled = [[RACSignal

                      combineLatest:@[ enabledSignal, moreExecutionsAllowed ]]

                      and];

这里涉及到了combineLatest:的变换操作,这个操作在之前的文章里面分析过了,这里不再详细分析源码实现。combineLatest:的作用就是把后面数组里面传入的每个信号,不管是谁发送出来一个信号,都会把数组里面所有信号的最新的值组合到一个RACTuple里面。immediateEnabled会把每个RACTuple里面的元素都进行逻辑and运算,这样immediateEnabled信号里面装的也都是BOOL值了。

immediateEnabled信号的意义就是每时每刻监听RACCommand是否可以enabled。它是由2个信号进行and操作得来的。每当allowsConcurrentExecution变化的时候就会产生一个信号,此时再加上enabledSignal信号,就能判断这一刻RACCommand是否能够enabled。每当enabledSignal变化的时候也会产生一个信号,再加上allowsConcurrentExecution是否允许并发,也能判断这一刻RACCommand是否能够enabled。所以immediateEnabled是由这两个信号combineLatest:之后再进行and操作得来的。

 

1

2

3

4

5

6

_enabled = [[[[[self.immediateEnabled

                take:1]

                concat:[[self.immediateEnabled skip:1] deliverOn:RACScheduler.mainThreadScheduler]]

                distinctUntilChanged]

                replayLast]

                setNameWithFormat:@"%@ -enabled", self];

由上面源码可以知道,self.immediateEnabled是由enabledSignal, moreExecutionsAllowed组合而成的。根据源码,enabledSignal的第一个信号值一定是[RACSignal return:@YES],moreExecutionsAllowed是RACObserve(self, allowsConcurrentExecution)产生的,由于allowsConcurrentExecution默认值是NO,所以moreExecutionsAllowed的第一个值是[immediateExecuting not]。

这里比较奇怪的地方是为何要用一次concat操作,把第一个信号值和后面的连接起来。如果直接写[self.immediateEnabled deliverOn:RACScheduler.mainThreadScheduler],那么整个self.immediateEnabled就都在主线程上了。作者既然没有这么写,肯定是有原因的。

This signal will send its current value upon subscription, and then all future values on the main thread.

通过查看文档,明白了作者的意图,作者的目的是为了让第一个值以后的每个值都发送在主线程上,所以这里skip:1之后接着deliverOn:RACScheduler.mainThreadScheduler。那第一个值呢?第一个值在一订阅的时候就发送出去了,同订阅者所在线程一致。

distinctUntilChanged保证enabled信号每次状态变化的时候只取到一个状态值。最后调用replayLast转换成只保存最新值的热信号。

从源码上看,enabled信号除去第一个值以外的每个值也都是在主线程上发送的。

三. execute:底层实现分析

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

- (RACSignal *)execute:(id)input {

    // 1

    BOOL enabled = [[self.immediateEnabled first] boolValue];

    if (!enabled) {

        NSError *error = [NSError errorWithDomain:RACCommandErrorDomain code:RACCommandErrorNotEnabled userInfo:@{

                          NSLocalizedDescriptionKey: NSLocalizedString(@"The command is disabled and cannot be executed", nil),RACUnderlyingCommandErrorKey: self }];

 

        return [RACSignal error:error];

    }

    // 2

    RACSignal *signal = self.signalBlock(input);

    NSCAssert(signal != nil, @"nil signal returned from signal block for value: %@", input);

    // 3

    RACMulticastConnection *connection = [[signal subscribeOn:RACScheduler.mainThreadScheduler] multicast:[RACReplaySubject subject]];

 

    @weakify(self);

    // 4

    [self addActiveExecutionSignal:connection.signal];

    [connection.signal subscribeError:^(NSError *error) {

        @strongify(self);

        // 5

        [self removeActiveExecutionSignal:connection.signal];

    } completed:^{

        @strongify(self);

        // 5

        [self removeActiveExecutionSignal:connection.signal];

    }];

 

    [connection connect];

     // 6

    return [connection.signal setNameWithFormat:@"%@ -execute: %@", self, [input rac_description]];

}

把上述代码分成6步来分析:

  1. self.immediateEnabled为了保证第一个值能正常的发送给订阅者,所以这里用了同步的first的方法,也是可以接受的。调用了first方法之后,根据这第一个值来判断RACCommand是否可以开始执行。如果不能执行就返回一个错误信号。
  2. 这里就是RACCommand开始执行的地方。self.signalBlock是RACCommand在初始化的时候传入的一个参数,RACSignal * (^signalBlock)(id input)这个闭包的入参是一个id input,返回值是一个信号。这里正好把execute的入参input传进来。
  3. 把RACCommand执行之后的信号先调用subscribeOn:保证didSubscribe block( )闭包在主线程中执行,再转换成RACMulticastConnection,准备转换成热信号。
  4. 在最终的信号被订阅者订阅之前,我们需要优先更新RACCommand里面的executing和enabled信号,所以这里要先把connection.signal加入到self.activeExecutionSignals数组里面。
  5. 订阅最终结果信号,出现错误或者完成,都要更新self.activeExecutionSignals数组。
  6. 这里想说明的是,最终的execute:返回的信号,和executionSignals是一样的。

四. RACCommand的一些Category

RACCommand在日常iOS开发过程中,很适合上下拉刷新,按钮点击等操作,所以ReactiveCocoa就帮我们在这些UI控件上封装了一个RACCommand属性——rac_command。

1. UIBarButtonItem+RACCommandSupport

一旦UIBarButtonItem被点击,RACCommand就会执行。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

- (RACCommand *)rac_command {

    return objc_getAssociatedObject(self, UIControlRACCommandKey);

}

 

- (void)setRac_command:(RACCommand *)command {

    objc_setAssociatedObject(self, UIControlRACCommandKey, command, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

 

    // 检查已经存储过的信号,移除老的,添加一个新的

    RACDisposable *disposable = objc_getAssociatedObject(self, UIControlEnabledDisposableKey);

    [disposable dispose];

 

    if (command == nil) return;

 

    disposable = [command.enabled setKeyPath:@keypath(self.enabled) onObject:self];

    objc_setAssociatedObject(self, UIControlEnabledDisposableKey, disposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

 

    [self rac_hijackActionAndTargetIfNeeded];

}

给UIBarButtonItem添加rac_command属性用到了runtime里面的AssociatedObject关联对象。这里给UIBarButtonItem类新增了2个关联对象,key分别是UIControlRACCommandKey,UIControlEnabledDisposableKey。UIControlRACCommandKey对应的是绑定的command,UIControlEnabledDisposableKey对应的是command.enabled的disposable信号。

set方法里面最后会调用rac_hijackActionAndTargetIfNeeded,这个方法需要特别注意:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

- (void)rac_hijackActionAndTargetIfNeeded {

    SEL hijackSelector = @selector(rac_commandPerformAction:);

    if (self.target == self && self.action == hijackSelector) return;

 

    if (self.target != nil) NSLog(@"WARNING: UIBarButtonItem.rac_command hijacks the control's existing target and action.");

 

        self.target = self;

        self.action = hijackSelector;

}

 

- (void)rac_commandPerformAction:(id)sender {

    [self.rac_command execute:sender];

}

rac_hijackActionAndTargetIfNeeded方法是对当前UIBarButtonItem的target和action进行检查。

如果当前UIBarButtonItem的target = self,并且action = @selector(rac_commandPerformAction:),那么就算检查通过符合执行RACCommand的前提条件了,直接return。

如果上述条件不符合,就强制改变UIBarButtonItem的target = self,并且action = @selector(rac_commandPerformAction:),所以这里需要注意的就是,UIBarButtonItem调用rac_command,会被强制改变它的target和action。

2. UIButton+RACCommandSupport

一旦UIButton被点击,RACCommand就会执行。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

- (RACCommand *)rac_command {

    return objc_getAssociatedObject(self, UIButtonRACCommandKey);

}

 

- (void)setRac_command:(RACCommand *)command {

    objc_setAssociatedObject(self, UIButtonRACCommandKey, command, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

 

    RACDisposable *disposable = objc_getAssociatedObject(self, UIButtonEnabledDisposableKey);

    [disposable dispose];

 

    if (command == nil) return;

 

    disposable = [command.enabled setKeyPath:@keypath(self.enabled) onObject:self];

    objc_setAssociatedObject(self, UIButtonEnabledDisposableKey, disposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

 

    [self rac_hijackActionAndTargetIfNeeded];

}

这里给UIButton添加绑定2个属性同样也用到了runtime里面的AssociatedObject关联对象。代码和UIBarButtonItem的实现基本一样。同样是给UIButton类新增了2个关联对象,key分别是UIButtonRACCommandKey,UIButtonEnabledDisposableKey。UIButtonRACCommandKey对应的是绑定的command,UIButtonEnabledDisposableKey对应的是command.enabled的disposable信号。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

- (void)rac_hijackActionAndTargetIfNeeded {

    SEL hijackSelector = @selector(rac_commandPerformAction:);

 

    for (NSString *selector in [self actionsForTarget:self forControlEvent:UIControlEventTouchUpInside]) {

        if (hijackSelector == NSSelectorFromString(selector)) {

            return;

        }

    }

 

    [self addTarget:self action:hijackSelector forControlEvents:UIControlEventTouchUpInside];

}

 

- (void)rac_commandPerformAction:(id)sender {

    [self.rac_command execute:sender];

}

rac_hijackActionAndTargetIfNeeded函数的意思和之前的一样,也是检查UIButton的target和action。最终结果的UIButton的target = self,action = @selector(rac_commandPerformAction:)

3. UIRefreshControl+RACCommandSupport

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

- (RACCommand *)rac_command {

    return objc_getAssociatedObject(self, UIRefreshControlRACCommandKey);

}

 

- (void)setRac_command:(RACCommand *)command {

    objc_setAssociatedObject(self, UIRefreshControlRACCommandKey, command, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

 

    [objc_getAssociatedObject(self, UIRefreshControlDisposableKey) dispose];

 

    if (command == nil) return;

 

    RACDisposable *enabledDisposable = [command.enabled setKeyPath:@keypath(self.enabled) onObject:self];

 

    RACDisposable *executionDisposable = [[[[self

                                             rac_signalForControlEvents:UIControlEventValueChanged]

                                             map:^(UIRefreshControl *x) {

                                                return [[[command

                                                          execute:x]

                                                          catchTo:[RACSignal empty]]

                                                          then:^{

                                                            return [RACSignal return:x];

                                                        }];

                                            }]

                                            concat]

                                            subscribeNext:^(UIRefreshControl *x) {

                                              [x endRefreshing];

                                            }];

 

    RACDisposable *commandDisposable = [RACCompoundDisposable compoundDisposableWithDisposables:@[ enabledDisposable, executionDisposable ]];

    objc_setAssociatedObject(self, UIRefreshControlDisposableKey, commandDisposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

}

这里给UIRefreshControl添加绑定2个属性同样也用到了runtime里面的AssociatedObject关联对象。代码和UIBarButtonItem的实现基本一样。同样是给UIButton类新增了2个关联对象,key分别是UIRefreshControlRACCommandKey,UIRefreshControlDisposableKey。UIRefreshControlRACCommandKey对应的是绑定的command,UIRefreshControlDisposableKey对应的是command.enabled的disposable信号。

这里多了一个executionDisposable信号,这个信号是用来结束刷新操作的。

 

1

[[[command execute:x] catchTo:[RACSignal empty]] then:^{ return [RACSignal return:x]; }];

这个信号变换先把RACCommand执行,执行之后得到的结果信号剔除掉所有的错误。then操作就是忽略掉所有值,在最后添加一个返回UIRefreshControl对象的信号。

[self rac_signalForControlEvents:UIControlEventValueChanged]之后再map升阶为高阶信号,所以最后用concat降阶。最后订阅这个信号,订阅只会收到一个值,command执行完毕之后的信号发送完所有的值的时候,即收到这个值的时刻就是最终刷新结束的时刻。

所以最终的disposable信号还要加上executionDisposable。

你可能感兴趣的:(学习)