别人眼中的ReactiveCocoa上

ReactiveCocoa(其简称为RAC)是由Github 开源的一个应用于iOS和OS X开发的新框架。RAC具有函数式编程(FP)和响应式编程(RP)的特性。它主要吸取了.Net的 Reactive Extensions的设计和实现。

ReactiveCocoa 的宗旨是Streams of values over time ,随着时间变化而不断流动的数据流。

ReactiveCocoa 主要解决了以下这些问题:

  • UI数据绑定

UI控件通常需要绑定一个事件,RAC可以很方便的绑定任何数据流到控件上。

  • 用户交互事件绑定

RAC为可交互的UI控件提供了一系列能发送Signal信号的方法。这些数据流会在用户交互中相互传递。

  • 解决状态以及状态之间依赖过多的问题

有了RAC的绑定之后,可以不用在关心各种复杂的状态,isSelect,isFinish……也解决了这些状态在后期很难维护的问题。

  • 消息传递机制的大统一

OC中编程原来消息传递机制有以下几种:Delegate,Block Callback,Target-Action,Timers,KVO,objc上有一篇关于OC中这5种消息传递方式改如何选择的文章Communication Patterns,推荐大家阅读。现在有了RAC之后,以上这5种方式都可以统一用RAC来处理。

二. RAC中的核心RACSignal

ReactiveCocoa 中最核心的概念之一就是信号RACStream。RACRACStream中有两个子类——RACSignal 和 RACSequence。本文先来分析RACSignal。

我们会经常看到以下的代码:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

RACSignal *signal = [RACSignal createSignal:

                     ^RACDisposable *(id subscriber)

{

    [subscriber sendNext:@1];

    [subscriber sendNext:@2];

    [subscriber sendNext:@3];

    [subscriber sendCompleted];

    return [RACDisposable disposableWithBlock:^{

        NSLog(@"signal dispose");

    }];

}];

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

    NSLog(@"subscribe value = %@", x);

} error:^(NSError *error) {

    NSLog(@"error: %@", error);

} completed:^{

    NSLog(@"completed");

}];

 

[disposable dispose];

这是一个RACSignal被订阅的完整过程。被订阅的过程中,究竟发生了什么?

 

1

2

3

+ (RACSignal *)createSignal:(RACDisposable * (^)(id subscriber))didSubscribe {

return [RACDynamicSignal createSignal:didSubscribe];

}

RACSignal调用createSignal的时候,会调用RACDynamicSignal的createSignal的方法。

121194012-f1ff01334547c97d

RACDynamicSignal是RACSignal的子类。createSignal后面的参数是一个block。

 

1

(RACDisposable * (^)(id subscriber))didSubscribe

block的返回值是RACDisposable类型,block名叫didSubscribe。block的唯一一个参数是id类型的subscriber,这个subscriber是必须遵循RACSubscriber协议的。

RACSubscriber是一个协议,其下有以下4个协议方法:

 

1

2

3

4

5

6

7

8

9

@protocol RACSubscriber

@required

 

- (void)sendNext:(id)value;

- (void)sendError:(NSError *)error;

- (void)sendCompleted;

- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable;

 

@end

所以新建Signal的任务就全部落在了RACSignal的子类RACDynamicSignal上了。

 

1

2

3

4

@interface RACDynamicSignal ()

// The block to invoke for each subscriber.

@property (nonatomic, copy, readonly) RACDisposable * (^didSubscribe)(id subscriber);

@end

RACDynamicSignal这个类很简单,里面就保存了一个名字叫didSubscribe的block。

 

1

2

3

4

5

+ (RACSignal *)createSignal:(RACDisposable * (^)(id subscriber))didSubscribe {

     RACDynamicSignal *signal = [[self alloc] init];

     signal->_didSubscribe = [didSubscribe copy];

     return [signal setNameWithFormat:@"+createSignal:"];

}

这个方法中新建了一个RACDynamicSignal对象signal,并把传进来的didSubscribe这个block保存进刚刚新建对象signal里面的didSubscribe属性中。最后再给signal命名+createSignal:。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

- (instancetype)setNameWithFormat:(NSString *)format, ... {

if (getenv("RAC_DEBUG_SIGNAL_NAMES") == NULL) return self;

 

   NSCParameterAssert(format != nil);

 

   va_list args;

   va_start(args, format);

 

   NSString *str = [[NSString alloc] initWithFormat:format arguments:args];

   va_end(args);

 

   self.name = str;

   return self;

}

setNameWithFormat是RACStream里面的方法,由于RACDynamicSignal继承自RACSignal,所以它也能调用这个方法。

131194012-6b8e647845818c64

RACSignal的block就这样被保存起来了,那什么时候会被执行呢?

141194012-31b913f259e9e23c

block闭包在订阅的时候才会被“释放”出来。

RACSignal调用subscribeNext方法,返回一个RACDisposable。

 

1

2

3

4

5

6

7

8

- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock {

   NSCParameterAssert(nextBlock != NULL);

   NSCParameterAssert(errorBlock != NULL);

   NSCParameterAssert(completedBlock != NULL);

 

   RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:errorBlock completed:completedBlock];

   return [self subscribe:o];

}

在这个方法中会新建一个RACSubscriber对象,并传入nextBlock,errorBlock,completedBlock。

 

1

2

3

4

5

6

7

8

9

@interface RACSubscriber ()

 

// These callbacks should only be accessed while synchronized on self.

@property (nonatomic, copy) void (^next)(id value);

@property (nonatomic, copy) void (^error)(NSError *error);

@property (nonatomic, copy) void (^completed)(void);

@property (nonatomic, strong, readonly) RACCompoundDisposable *disposable;

 

@end

RACSubscriber这个类很简单,里面只有4个属性,分别是nextBlock,errorBlock,completedBlock和一个RACCompoundDisposable信号。

 

1

2

3

4

5

6

7

8

9

+ (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed {

RACSubscriber *subscriber = [[self alloc] init];

 

   subscriber->_next = [next copy];

   subscriber->_error = [error copy];

   subscriber->_completed = [completed copy];

 

   return subscriber;

}

 

151194012-80c70668e5c57c75

subscriberWithNext方法把传入的3个block都保存分别保存到自己对应的block中。

RACSignal调用subscribeNext方法,最后return的时候,会调用[self subscribe:o],这里实际是调用了RACDynamicSignal类里面的subscribe方法。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

- (RACDisposable *)subscribe:(id)subscriber {

NSCParameterAssert(subscriber != nil);

 

   RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];

   subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];

 

   if (self.didSubscribe != NULL) {

      RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{

      RACDisposable *innerDisposable = self.didSubscribe(subscriber);

      [disposable addDisposable:innerDisposable];

  }];

 

    [disposable addDisposable:schedulingDisposable];

}

 

return disposable;

}

RACDisposable有3个子类,其中一个就是RACCompoundDisposable。

161194012-5bd66869465f5ed3

 

 

1

2

3

4

5

6

@interface RACCompoundDisposable : RACDisposable

+ (instancetype)compoundDisposable;

+ (instancetype)compoundDisposableWithDisposables:(NSArray *)disposables;

- (void)addDisposable:(RACDisposable *)disposable;

- (void)removeDisposable:(RACDisposable *)disposable;

@end

RACCompoundDisposable虽然是RACDisposable的子类,但是它里面可以加入多个RACDisposable对象,在必要的时候可以一口气都调用dispose方法来销毁信号。当RACCompoundDisposable对象被dispose的时候,也会自动dispose容器内的所有RACDisposable对象。

RACPassthroughSubscriber是一个私有的类。

 

1

2

3

4

5

6

@interface RACPassthroughSubscriber : NSObject

@property (nonatomic, strong, readonly) id innerSubscriber;

@property (nonatomic, unsafe_unretained, readonly) RACSignal *signal;

@property (nonatomic, strong, readonly) RACCompoundDisposable *disposable;

- (instancetype)initWithSubscriber:(id)subscriber signal:(RACSignal *)signal disposable:(RACCompoundDisposable *)disposable;

@end

RACPassthroughSubscriber类就只有这一个方法。目的就是为了把所有的信号事件从一个订阅者subscriber传递给另一个还没有disposed的订阅者subscriber。

RACPassthroughSubscriber类中保存了3个非常重要的对象,RACSubscriber,RACSignal,RACCompoundDisposable。RACSubscriber是待转发的信号的订阅者subscriber。RACCompoundDisposable是订阅者的销毁对象,一旦它被disposed了,innerSubscriber就再也接受不到事件流了。

这里需要注意的是内部还保存了一个RACSignal,并且它的属性是unsafe_unretained。这里和其他两个属性有区别, 其他两个属性都是strong的。这里之所以不是weak,是因为引用RACSignal仅仅只是一个DTrace probes动态跟踪技术的探针。如果设置成weak,会造成没必要的性能损失。所以这里仅仅是unsafe_unretained就够了。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

- (instancetype)initWithSubscriber:(id)subscriber signal:(RACSignal *)signal disposable:(RACCompoundDisposable *)disposable {

   NSCParameterAssert(subscriber != nil);

 

   self = [super init];

   if (self == nil) return nil;

 

   _innerSubscriber = subscriber;

   _signal = signal;

   _disposable = disposable;

 

   [self.innerSubscriber didSubscribeWithDisposable:self.disposable];

   return self;

}

回到RACDynamicSignal类里面的subscribe方法中,现在新建好了RACCompoundDisposable和RACPassthroughSubscriber对象了。

 

1

2

3

4

5

6

7

8

if (self.didSubscribe != NULL) {

  RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{

   RACDisposable *innerDisposable = self.didSubscribe(subscriber);

   [disposable addDisposable:innerDisposable];

  }];

 

  [disposable addDisposable:schedulingDisposable];

}

RACScheduler.subscriptionScheduler是一个全局的单例。

 

1

2

3

4

5

6

7

8

9

+ (instancetype)subscriptionScheduler {

   static dispatch_once_t onceToken;

   static RACScheduler *subscriptionScheduler;

   dispatch_once(&onceToken, ^{

    subscriptionScheduler = [[RACSubscriptionScheduler alloc] init];

   });

 

   return subscriptionScheduler;

}

RACScheduler再继续调用schedule方法。

 

1

2

3

4

5

6

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

   NSCParameterAssert(block != NULL);

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

   block();

   return nil;

}

 

 

1

2

3

4

5

6

7

8

9

10

11

+ (BOOL)isOnMainThread {

return [NSOperationQueue.currentQueue isEqual:NSOperationQueue.mainQueue] || [NSThread isMainThread];

}

 

+ (instancetype)currentScheduler {

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

if (scheduler != nil) return scheduler;

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

 

return nil;

}

在取currentScheduler的过程中,会判断currentScheduler是否存在,和是否在主线程中。如果都没有,那么就会调用后台backgroundScheduler去执行schedule。

schedule的入参就是一个block,执行schedule的时候会去执行block。也就是会去执行:

 

1

2

RACDisposable *innerDisposable = self.didSubscribe(subscriber);

   [disposable addDisposable:innerDisposable];

这两句关键的语句。之前信号里面保存的block就会在此处被“释放”执行。self.didSubscribe(subscriber)这一句就执行了信号保存的didSubscribe闭包。

在didSubscribe闭包中有sendNext,sendError,sendCompleted,执行这些语句会分别调用RACPassthroughSubscriber里面对应的方法。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

- (void)sendNext:(id)value {

if (self.disposable.disposed) return;

if (RACSIGNAL_NEXT_ENABLED()) {

  RACSIGNAL_NEXT(cleanedSignalDescription(self.signal), cleanedDTraceString(self.innerSubscriber.description), cleanedDTraceString([value description]));

}

[self.innerSubscriber sendNext:value];

}

 

- (void)sendError:(NSError *)error {

if (self.disposable.disposed) return;

if (RACSIGNAL_ERROR_ENABLED()) {

  RACSIGNAL_ERROR(cleanedSignalDescription(self.signal), cleanedDTraceString(self.innerSubscriber.description), cleanedDTraceString(error.description));

}

[self.innerSubscriber sendError:error];

}

 

- (void)sendCompleted {

if (self.disposable.disposed) return;

if (RACSIGNAL_COMPLETED_ENABLED()) {

  RACSIGNAL_COMPLETED(cleanedSignalDescription(self.signal), cleanedDTraceString(self.innerSubscriber.description));

}

[self.innerSubscriber sendCompleted];

}

这个时候的订阅者是RACPassthroughSubscriber。RACPassthroughSubscriber里面的innerSubscriber才是最终的实际订阅者,RACPassthroughSubscriber会把值再继续传递给innerSubscriber。

 

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

- (void)sendNext:(id)value {

@synchronized (self) {

  void (^nextBlock)(id) = [self.next copy];

  if (nextBlock == nil) return;

 

  nextBlock(value);

}

}

 

- (void)sendError:(NSError *)e {

@synchronized (self) {

  void (^errorBlock)(NSError *) = [self.error copy];

  [self.disposable dispose];

 

  if (errorBlock == nil) return;

  errorBlock(e);

}

}

 

- (void)sendCompleted {

@synchronized (self) {

  void (^completedBlock)(void) = [self.completed copy];

  [self.disposable dispose];

 

  if (completedBlock == nil) return;

  completedBlock();

}

}

innerSubscriber是RACSubscriber,调用sendNext的时候会先把自己的self.next闭包copy一份,再调用,而且整个过程还是线程安全的,用@synchronized保护着。最终订阅者的闭包在这里被调用。

sendError和sendCompleted也都是同理。

总结一下:

171194012-a45b4ca902c91765

 

  1. RACSignal调用subscribeNext方法,新建一个RACSubscriber。
  2. 新建的RACSubscriber会copy,nextBlock,errorBlock,completedBlock存在自己的属性变量中。
  3. RACSignal的子类RACDynamicSignal调用subscribe方法。
  4. 新建RACCompoundDisposable和RACPassthroughSubscriber对象。RACPassthroughSubscriber分别保存对RACSignal,RACSubscriber,RACCompoundDisposable的引用,注意对RACSignal的引用是unsafe_unretained的。
  5. RACDynamicSignal调用didSubscribe闭包。先调用RACPassthroughSubscriber的相应的sendNext,sendError,sendCompleted方法。
  6. RACPassthroughSubscriber再去调用self.innerSubscriber,即RACSubscriber的nextBlock,errorBlock,completedBlock。注意这里调用同样是先copy一份,再调用闭包执行。

三. RACSignal操作的核心bind实现

181194012-1f9c2dad954a5749

在RACSignal的源码里面包含了两个基本操作,concat和zipWith。不过在分析这两个操作之前,先来分析一下更加核心的一个函数,bind操作。

先来说说bind函数的作用:

  1. 会订阅原始的信号。
  2. 任何时刻原始信号发送一个值,都会绑定的block转换一次。
  3. 一旦绑定的block转换了值变成信号,就立即订阅,并把值发给订阅者subscriber。
  4. 一旦绑定的block要终止绑定,原始的信号就complete。
  5. 当所有的信号都complete,发送completed信号给订阅者subscriber。
  6. 如果中途信号出现了任何error,都要把这个错误发送给subscriber

 

 

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

- (RACSignal *)bind:(RACStreamBindBlock (^)(void))block {

NSCParameterAssert(block != NULL);

 

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

  RACStreamBindBlock bindingBlock = block();

 

  NSMutableArray *signals = [NSMutableArray arrayWithObject:self];

 

  RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable];

 

  void (^completeSignal)(RACSignal *, RACDisposable *) = ^(RACSignal *signal, RACDisposable *finishedDisposable) { /*这里暂时省略*/ };

  void (^addSignal)(RACSignal *) = ^(RACSignal *signal) { /*这里暂时省略*/ };

 

  @autoreleasepool {

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

   [compoundDisposable addDisposable:selfDisposable];

 

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

    // Manually check disposal to handle synchronous errors.

    if (compoundDisposable.disposed) return;

 

    BOOL stop = NO;

    id signal = bindingBlock(x, &stop);

 

    @autoreleasepool {

     if (signal != nil) addSignal(signal);

     if (signal == nil || stop) {

      [selfDisposable dispose];

      completeSignal(self, selfDisposable);

     }

    }

   } error:^(NSError *error) {

    [compoundDisposable dispose];

    [subscriber sendError:error];

   } completed:^{

    @autoreleasepool {

     completeSignal(self, selfDisposable);

    }

   }];

 

   selfDisposable.disposable = bindingDisposable;

  }

 

  return compoundDisposable;

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

}

为了弄清楚bind函数究竟做了什么,写出测试代码:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

    RACSignal *signal = [RACSignal createSignal:

                         ^RACDisposable *(id subscriber)

    {

        [subscriber sendNext:@1];

        [subscriber sendNext:@2];

        [subscriber sendNext:@3];

        [subscriber sendCompleted];

        return [RACDisposable disposableWithBlock:^{

            NSLog(@"signal dispose");

        }];

    }];

 

    RACSignal *bindSignal = [signal bind:^RACStreamBindBlock{

        return ^RACSignal *(NSNumber *value, BOOL *stop){

            value = @(value.integerValue * 2);

            return [RACSignal return:value];

        };

    }];

 

    [bindSignal subscribeNext:^(id x) {

        NSLog(@"subscribe value = %@", x);

    }];

由于前面第一章节详细讲解了RACSignal的创建和订阅的全过程,这个也为了方法讲解,创建RACDynamicSignal,RACCompoundDisposable,RACPassthroughSubscriber这些都略过,这里着重分析一下bind的各个闭包传递创建和订阅的过程。

为了防止接下来的分析会让读者看晕,这里先把要用到的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

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

    RACSignal *signal = [RACSignal createSignal:

                         ^RACDisposable *(id subscriber)

    {

        // block 1

    }

 

    RACSignal *bindSignal = [signal bind:^RACStreamBindBlock{

        // block 2

        return ^RACSignal *(NSNumber *value, BOOL *stop){

            // block 3

        };

    }];

 

    [bindSignal subscribeNext:^(id x) {

        // block 4

    }];

 

- (RACSignal *)bind:(RACStreamBindBlock (^)(void))block {

        // block 5

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

        // block 6

        RACStreamBindBlock bindingBlock = block();

        NSMutableArray *signals = [NSMutableArray arrayWithObject:self];

 

        void (^completeSignal)(RACSignal *, RACDisposable *) = ^(RACSignal *signal, RACDisposable *finishedDisposable) {

        // block 7

        };

 

        void (^addSignal)(RACSignal *) = ^(RACSignal *signal) {

        // block 8

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

            // block 9

            }];

        };

 

        @autoreleasepool {

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

                // block 10

                id signal = bindingBlock(x, &stop);

 

                @autoreleasepool {

                    if (signal != nil) addSignal(signal);

                    if (signal == nil || stop) {

                        [selfDisposable dispose];

                        completeSignal(self, selfDisposable);

                    }

                }

            } error:^(NSError *error) {

                [compoundDisposable dispose];

                [subscriber sendError:error];

            } completed:^{

                @autoreleasepool {

                    completeSignal(self, selfDisposable);

                }

            }];

        }

        return compoundDisposable;

    }] ;

}

先创建信号signal,didSubscribe把block1 copy保存起来。

当信号调用bind进行绑定,会调用block5,didSubscribe把block6 copy保存起来。

当订阅者开始订阅bindSignal的时候,流程如下:

  1. bindSignal执行didSubscribe的block,即执行block6。
  2. 在block6 的第一句代码,就是调用RACStreamBindBlock bindingBlock = block(),这里的block是外面传进来的block2,于是开始调用block2。执行完block2,会返回一个RACStreamBindBlock的对象。
  3. 由于是signal调用的bind函数,所以bind函数里面的self就是signal,在bind内部订阅了signal的信号。subscribeNext所以会执行block1。
  4. 执行block1,sendNext调用订阅者subscriber的nextBlock,于是开始执行block10。
  5. block10中会先调用bindingBlock,这个是之前调用block2的返回值,这个RACStreamBindBlock对象里面保存的是block3。所以开始调用block3。
  6. 在block3中入参是一个value,这个value是signal中sendNext中发出来的value的值,在block3中可以对value进行变换,变换完成后,返回一个新的信号signal’。
  7. 如果返回的signal’为空,则会调用completeSignal,即调用block7。block7中会发送sendCompleted。如果返回的signal’不为空,则会调用addSignal,即调用block8。block8中会继续订阅signal’。执行block9。
  8. block9 中会sendNext,这里的subscriber是block6的入参,于是对subscriber调用sendNext,会调用到bindSignal的订阅者的block4中。
  9. block9 中执行完sendNext,还会调用sendCompleted。这里的是在执行block9里面的completed闭包。completeSignal(signal, selfDisposable);然后又会调用completeSignal,即block7。
  10. 执行完block7,就完成了一次从signal 发送信号sendNext的全过程。

bind整个流程就完成了。

四. RACSignal基本操作concat和zipWith实现

接下来再来分析RACSignal中另外2个基本操作。

1. concat

191194012-0ef5a667330ba32c

写出测试代码:

 

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 *signal = [RACSignal createSignal:

                         ^RACDisposable *(id subscriber)

    {

        [subscriber sendNext:@1];

        [subscriber sendCompleted];

        return [RACDisposable disposableWithBlock:^{

            NSLog(@"signal dispose");

        }];

    }];

 

 

    RACSignal *signals = [RACSignal createSignal:

                         ^RACDisposable *(id subscriber)

    {

        [subscriber sendNext:@2];

        [subscriber sendNext:@3];

        [subscriber sendNext:@6];

        [subscriber sendCompleted];

        return [RACDisposable disposableWithBlock:^{

            NSLog(@"signal dispose");

        }];

    }];

 

    RACSignal *concatSignal = [signal concat:signals];

 

    [concatSignal subscribeNext:^(id x) {

        NSLog(@"subscribe value = %@", x);

    }];

concat操作就是把两个信号合并起来。注意合并有先后顺序。

201194012-bad211553b6d3674

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

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

}

合并前,signal和signals分别都把各自的didSubscribe保存copy起来。
合并之后,合并之后新的信号的didSubscribe会把block保存copy起来。

当合并之后的信号被订阅的时候:

  1. 调用新的合并信号的didSubscribe。
  2. 由于是第一个信号调用的concat方法,所以block中的self是前一个信号signal。合并信号的didSubscribe会先订阅signal。
  3. 由于订阅了signal,于是开始执行signal的didSubscribe,sendNext,sendError。
  4. 当前一个信号signal发送sendCompleted之后,就会开始订阅后一个信号signals,调用signals的didSubscribe。
  5. 由于订阅了后一个信号,于是后一个信号signals开始发送sendNext,sendError,sendCompleted。

这样两个信号就前后有序的拼接到了一起。

这里有二点需要注意的是:

  1. 只有当第一个信号完成之后才能收到第二个信号的值,因为第二个信号是在第一个信号completed的闭包里面订阅的,所以第一个信号不结束,第二个信号也不会被订阅。
  2. 两个信号concat在一起之后,新的信号的结束信号在第二个信号结束的时候才结束。看上图描述,新的信号的发送长度等于前面两个信号长度之和,concat之后的新信号的结束信号也就是第二个信号的结束信号。

2. zipWith

211194012-5e568beb91ed74d4

写出测试代码:

 

1

2

3

4

5

    RACSignal *concatSignal = [signal zipWith:signals];

 

    [concatSignal subscribeNext:^(id x) {

        NSLog(@"subscribe value = %@", x);

    }];

 

221194012-cf1c6bfd2ad53bc5

源码如下:

 

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

75

76

77

78

79

80

81

82

83

84

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

    NSCParameterAssert(signal != nil);

 

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

        __block BOOL selfCompleted = NO;

        NSMutableArray *selfValues = [NSMutableArray array];

 

        __block BOOL otherCompleted = NO;

        NSMutableArray *otherValues = [NSMutableArray array];

 

        void (^sendCompletedIfNecessary)(void) = ^{

            @synchronized (selfValues) {

                BOOL selfEmpty = (selfCompleted && selfValues.count == 0);

                BOOL otherEmpty = (otherCompleted && otherValues.count == 0);

 

                // 如果任意一个信号完成并且数组里面空了,就整个信号算完成

                if (selfEmpty || otherEmpty) [subscriber sendCompleted];

            }

        };

 

        void (^sendNext)(void) = ^{

            @synchronized (selfValues) {

 

                // 数组里面的空了就返回。

                if (selfValues.count == 0) return;

                if (otherValues.count == 0) return;

 

                // 每次都取出两个数组里面的第0位的值,打包成元组

                RACTuple *tuple = RACTuplePack(selfValues[0], otherValues[0]);

                [selfValues removeObjectAtIndex:0];

                [otherValues removeObjectAtIndex:0];

 

                // 把元组发送出去

                [subscriber sendNext:tuple];

                sendCompletedIfNecessary();

            }

        };

 

        // 订阅第一个信号

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

            @synchronized (selfValues) {

 

                // 把第一个信号的值加入到数组中

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

                sendNext();

            }

        } error:^(NSError *error) {

            [subscriber sendError:error];

        } completed:^{

            @synchronized (selfValues) {

 

                // 订阅完成时判断是否要发送完成信号

                selfCompleted = YES;

                sendCompletedIfNecessary();

            }

        }];

 

        // 订阅第二个信号

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

            @synchronized (selfValues) {

 

                // 把第二个信号加入到数组中

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

                sendNext();

            }

        } error:^(NSError *error) {

            [subscriber sendError:error];

        } completed:^{

            @synchronized (selfValues) {

 

                // 订阅完成时判断是否要发送完成信号

                otherCompleted = YES;

                sendCompletedIfNecessary();

            }

        }];

 

        return [RACDisposable disposableWithBlock:^{

 

            // 销毁两个信号

            [selfDisposable dispose];

            [otherDisposable dispose];

        }];

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

}

当把两个信号通过zipWith之后,就像上面的那张图一样,拉链的两边被中间的拉索拉到了一起。既然是拉链,那么一一的位置是有对应的,上面的拉链第一个位置只能对着下面拉链第一个位置,这样拉链才能拉到一起去。

具体实现:

zipWith里面有两个数组,分别会存储两个信号的值。

  1. 一旦订阅了zipWith之后的信号,就开始执行didSubscribe闭包。
  2. 在闭包中会先订阅第一个信号。这里假设第一个信号比第二个信号先发出一个值。第一个信号发出来的每一个值都会被加入到第一个数组中保存起来,然后调用sendNext( )闭包。在sendNext( )闭包中,会先判断两个数组里面是否都为空,如果有一个数组里面是空的,就return。由于第二个信号还没有发送值,即第二个信号的数组里面是空的,所以这里第一个值发送不出来。于是第一个信号被订阅之后,发送的值存储到了第一个数组里面了,没有发出去。
  3. 第二个信号的值紧接着发出来了,第二个信号每发送一次值,也会存储到第二个数组中,但是这个时候再调用sendNext( )闭包的时候,不会再return了,因为两个数组里面都有值了,两个数组的第0号位置都有一个值了。有值以后就打包成元组RACTuple发送出去。并清空两个数组0号位置存储的值。
  4. 以后两个信号每次发送一个,就先存储在数组中,只要有“配对”的另一个信号,就一起打包成元组RACTuple发送出去。从图中也可以看出,zipWith之后的新信号,每个信号的发送时刻是等于两个信号最晚发出信号的时刻。
  5. 新信号的完成时间,是当两者任意一个信号完成并且数组里面为空,就算完成了。所以最后第一个信号发送的5的那个值就被丢弃了。

第一个信号依次发送的1,2,3,4的值和第二个信号依次发送的A,B,C,D的值,一一的合在了一起,就像拉链把他们拉在一起。由于5没法配对,所以拉链也拉不上了。

目录

  • 1.关于冷信号和热信号的概念
  • 2.RACSignal热信号
  • 3.RACSignal冷信号
  • 4.冷信号是如何转换成热信号的

一. 关于冷信号和热信号的概念

121194012-bba4a65ea0b2b754

冷热信号的概念是源自于源于.NET框架Reactive Extensions(RX)中的Hot Observable和Cold Observable,

Hot Observable是主动的,尽管你并没有订阅事件,但是它会时刻推送,就像鼠标移动;而Cold Observable是被动的,只有当你订阅的时候,它才会发布消息。

Hot Observable可以有多个订阅者,是一对多,集合可以与订阅者共享信息;而Cold Observable只能一对一,当有不同的订阅者,消息是重新完整发送。

在这篇文章细说ReactiveCocoa的冷信号与热信号(一)详细分析了冷热信号的特点:

热信号是主动的,即使你没有订阅事件,它仍然会时刻推送。而冷信号是被动的,只有当你订阅的时候,它才会发送消息。

热信号可以有多个订阅者,是一对多,信号可以与订阅者共享信息。而冷信号只能一对一,当有不同的订阅者,消息会从新完整发送。

二. RACSignal热信号

131194012-04d1e539c0734caa

RACSignal家族中符合热信号的特点的信号有以下几个。

1.RACSubject

 

 

1

2

3

4

5

6

7

8

9

@interface RACSubject : RACSignal

 

@property (nonatomic, strong, readonly) NSMutableArray *subscribers;

@property (nonatomic, strong, readonly) RACCompoundDisposable *disposable;

 

- (void)enumerateSubscribersUsingBlock:(void (^)(id subscriber))block;

+ (instancetype)subject;

 

@end

首先来看看RACSubject的定义。

RACSubject是继承自RACSignal,并且它还遵守RACSubscriber协议。这就意味着它既能订阅信号,也能发送信号。

在RACSubject里面有一个NSMutableArray数组,里面装着该信号的所有订阅者。其次还有一个RACCompoundDisposable信号,里面装着该信号所有订阅者的RACDisposable。

RACSubject之所以能称之为热信号,那么它肯定是符合上述热信号的定义的。让我们从它的实现来看看它是如何符合的。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

- (RACDisposable *)subscribe:(id)subscriber {

    NSCParameterAssert(subscriber != nil);

 

    RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];

    subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];

 

    NSMutableArray *subscribers = self.subscribers;

    @synchronized (subscribers) {

        [subscribers addObject:subscriber];

    }

 

    return [RACDisposable disposableWithBlock:^{

        @synchronized (subscribers) {

            NSUInteger index = [subscribers indexOfObjectWithOptions:NSEnumerationReverse passingTest:^ BOOL (id obj, NSUInteger index, BOOL *stop) {

                return obj == subscriber;

            }];

 

            if (index != NSNotFound) [subscribers removeObjectAtIndex:index];

        }

    }];

}

上面是RACSubject的实现,它和RACSignal最大的不同在这两行

 

1

2

3

4

NSMutableArray *subscribers = self.subscribers;

@synchronized (subscribers) {

    [subscribers addObject:subscriber];

}

RACSubject 把它的所有订阅者全部都保存到了NSMutableArray的数组里。既然保存了所有的订阅者,那么sendNext,sendError,sendCompleted就需要发生改变。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

- (void)sendNext:(id)value {

    [self enumerateSubscribersUsingBlock:^(id subscriber) {

        [subscriber sendNext:value];

    }];

}

 

- (void)sendError:(NSError *)error {

    [self.disposable dispose];

 

    [self enumerateSubscribersUsingBlock:^(id subscriber) {

        [subscriber sendError:error];

    }];

}

 

- (void)sendCompleted {

    [self.disposable dispose];

 

    [self enumerateSubscribersUsingBlock:^(id subscriber) {

        [subscriber sendCompleted];

    }];

}

从源码可以看到,RACSubject中的sendNext,sendError,sendCompleted都会执行enumerateSubscribersUsingBlock:方法。

 

1

2

3

4

5

6

7

8

9

10

- (void)enumerateSubscribersUsingBlock:(void (^)(id subscriber))block {

    NSArray *subscribers;

    @synchronized (self.subscribers) {

        subscribers = [self.subscribers copy];

    }

 

    for (id subscriber in subscribers) {

        block(subscriber);

    }

}

enumerateSubscribersUsingBlock:方法会取出所有RACSubject的订阅者,依次调用入参的block( )方法。

关于RACSubject的订阅和发送的流程可以参考第一篇文章,大体一致,其他的不同就是会依次对自己的订阅者发送信号。

RACSubject就满足了热信号的特点,它即使没有订阅者,因为自己继承了RACSubscriber协议,所以自己本身就可以发送信号。冷信号只能被订阅了才能发送信号。

RACSubject可以有很多订阅者,它也会把这些订阅者都保存到自己的数组里。RACSubject之后再发送信号,订阅者就如同一起看电视,播放过的节目就看不到了,发送过的信号也接收不到了。接收信号。而RACSignal发送信号,订阅者接收信号都只能从头开始接受,如同看点播节目,每次看都从头开始看。

2. RACGroupedSignal

 

 

1

2

3

4

5

@interface RACGroupedSignal : RACSubject

 

@property (nonatomic, readonly, copy) id key;

+ (instancetype)signalWithKey:(id)key;

@end

先看看RACGroupedSignal的定义。

RACGroupedSignal是在RACsignal这个方法里面被用到的。

 

1

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

在这个方法里面,sendNext里面最后里面是由RACGroupedSignal发送信号。

 

1

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

关于groupBy的详细分析请看这篇文章

3. RACBehaviorSubject

 

 

1

2

3

4

@interface RACBehaviorSubject : RACSubject

@property (nonatomic, strong) id currentValue;

+ (instancetype)behaviorSubjectWithDefaultValue:(id)value;

@end

这个信号里面存储了一个对象currentValue,这里存储着这个信号的最新的值。

当然也可以调用类方法behaviorSubjectWithDefaultValue

 

1

2

3

4

5

+ (instancetype)behaviorSubjectWithDefaultValue:(id)value {

    RACBehaviorSubject *subject = [self subject];

    subject.currentValue = value;

    return subject;

}

在这个方法里面存储默认的值,如果RACBehaviorSubject没有接受到任何值,那么这个信号就会发送这个默认的值。

当RACBehaviorSubject被订阅:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

- (RACDisposable *)subscribe:(id)subscriber {

    RACDisposable *subscriptionDisposable = [super subscribe:subscriber];

 

    RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{

        @synchronized (self) {

            [subscriber sendNext:self.currentValue];

        }

    }];

 

    return [RACDisposable disposableWithBlock:^{

        [subscriptionDisposable dispose];

        [schedulingDisposable dispose];

    }];

}

sendNext里面会始终发送存储的currentValue值。调用sendNext会调用RACSubject里面的sendNext,也会依次发送信号值给订阅数组里面每个订阅者。

当RACBehaviorSubject向订阅者sendNext的时候:

 

1

2

3

4

5

6

- (void)sendNext:(id)value {

    @synchronized (self) {

        self.currentValue = value;

        [super sendNext:value];

    }

}

RACBehaviorSubject会把发送的值更新到currentValue里面。下次发送值就会发送最后更新的值。

4. RACReplaySubject

 

 

1

2

3

4

5

6

7

8

9

10

11

const NSUInteger RACReplaySubjectUnlimitedCapacity = NSUIntegerMax;

@interface RACReplaySubject : RACSubject

 

@property (nonatomic, assign, readonly) NSUInteger capacity;

@property (nonatomic, strong, readonly) NSMutableArray *valuesReceived;

@property (nonatomic, assign) BOOL hasCompleted;

@property (nonatomic, assign) BOOL hasError;

@property (nonatomic, strong) NSError *error;

+ (instancetype)replaySubjectWithCapacity:(NSUInteger)capacity;

 

@end

RACReplaySubject中会存储RACReplaySubjectUnlimitedCapacity大小的历史值。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

+ (instancetype)replaySubjectWithCapacity:(NSUInteger)capacity {

    return [(RACReplaySubject *)[self alloc] initWithCapacity:capacity];

}

 

- (instancetype)init {

    return [self initWithCapacity:RACReplaySubjectUnlimitedCapacity];

}

 

- (instancetype)initWithCapacity:(NSUInteger)capacity {

    self = [super init];

    if (self == nil) return nil;

 

    _capacity = capacity;

    _valuesReceived = (capacity == RACReplaySubjectUnlimitedCapacity ? [NSMutableArray array] : [NSMutableArray arrayWithCapacity:capacity]);

 

    return self;

}

在RACReplaySubject初始化中会初始化一个capacity大小的数组。

 

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

- (RACDisposable *)subscribe:(id)subscriber {

    RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable];

 

    RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{

        @synchronized (self) {

            for (id value in self.valuesReceived) {

                if (compoundDisposable.disposed) return;

 

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

            }

 

            if (compoundDisposable.disposed) return;

 

            if (self.hasCompleted) {

                [subscriber sendCompleted];

            } else if (self.hasError) {

                [subscriber sendError:self.error];

            } else {

                RACDisposable *subscriptionDisposable = [super subscribe:subscriber];

                [compoundDisposable addDisposable:subscriptionDisposable];

            }

        }

    }];

 

    [compoundDisposable addDisposable:schedulingDisposable];

 

    return compoundDisposable;

}

当RACReplaySubject被订阅的时候,会把valuesReceived数组里面的值都发送出去。

 

1

2

3

4

5

6

7

8

9

10

- (void)sendNext:(id)value {

    @synchronized (self) {

        [self.valuesReceived addObject:value ?: RACTupleNil.tupleNil];

        [super sendNext:value];

 

        if (self.capacity != RACReplaySubjectUnlimitedCapacity && self.valuesReceived.count > self.capacity) {

            [self.valuesReceived removeObjectsInRange:NSMakeRange(0, self.valuesReceived.count - self.capacity)];

        }

    }

}

在sendNext中,valuesReceived会保存每次接收到的值。调用super的sendNext,会依次把值都发送到每个订阅者中。

这里还会判断数组里面存储了多少个值。如果存储的值的个数大于了capacity,那么要移除掉数组里面从0开始的前几个值,保证数组里面只装capacity个数的值。

RACReplaySubject 和 RACSubject 的区别在于,RACReplaySubject还会把历史的信号值都存储起来发送给订阅者。这一点,RACReplaySubject更像是RACSingnal 和 RACSubject 的合体版。RACSignal是冷信号,一旦被订阅就会向订阅者发送所有的值,这一点RACReplaySubject和RACSignal是一样的。但是RACReplaySubject又有着RACSubject的特性,会把所有的值发送给多个订阅者。当RACReplaySubject发送完之前存储的历史值之后,之后再发送信号的行为就和RACSubject完全一致了。

三. RACSignal冷信号

141194012-2c6a1589c975bd49

在ReactiveCocoa v2.5中除了RACsignal信号以外,还有一些特殊的冷信号。

1.RACEmptySignal

 

 

1

2

3

@interface RACEmptySignal : RACSignal

+ (RACSignal *)empty;

@end

这个信号只有一个empty方法。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

+ (RACSignal *)empty {

#ifdef DEBUG

    return [[[self alloc] init] setNameWithFormat:@"+empty"];

#else

    static id singleton;

    static dispatch_once_t pred;

 

    dispatch_once(&pred, ^{

        singleton = [[self alloc] init];

    });

 

    return singleton;

#endif

}

在debug模式下,返回一个名字叫empty的信号。在release模式下,返回一个单例的empty信号。

 

1

2

3

4

5

6

- (RACDisposable *)subscribe:(id)subscriber {

    NSCParameterAssert(subscriber != nil);

    return [RACScheduler.subscriptionScheduler schedule:^{

        [subscriber sendCompleted];

    }];

}

RACEmptySignal信号一旦被订阅就会发送sendCompleted。

2. RACReturnSignal

 

 

1

2

3

4

@interface RACReturnSignal : RACSignal

@property (nonatomic, strong, readonly) id value;

+ (RACSignal *)return:(id)value;

@end

RACReturnSignal信号的定义也很简单,直接根据value的值返回一个RACSignal。

 

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

+ (RACSignal *)return:(id)value {

#ifndef DEBUG

    if (value == RACUnit.defaultUnit) {

        static RACReturnSignal *unitSingleton;

        static dispatch_once_t unitPred;

 

        dispatch_once(&unitPred, ^{

            unitSingleton = [[self alloc] init];

            unitSingleton->_value = RACUnit.defaultUnit;

        });

 

        return unitSingleton;

    } else if (value == nil) {

        static RACReturnSignal *nilSingleton;

        static dispatch_once_t nilPred;

 

        dispatch_once(&nilPred, ^{

            nilSingleton = [[self alloc] init];

            nilSingleton->_value = nil;

        });

 

        return nilSingleton;

    }

#endif

 

    RACReturnSignal *signal = [[self alloc] init];

    signal->_value = value;

 

#ifdef DEBUG

    [signal setNameWithFormat:@"+return: %@", value];

#endif

 

    return signal;

}

在debug模式下直接新建一个RACReturnSignal信号里面的值存储的是入参value。在release模式下,会依照value的值是否是空,来新建对应的单例RACReturnSignal。

 

1

2

3

4

5

6

7

8

- (RACDisposable *)subscribe:(id)subscriber {

    NSCParameterAssert(subscriber != nil);

 

return [RACScheduler.subscriptionScheduler schedule:^{

    [subscriber sendNext:self.value];

    [subscriber sendCompleted];

}];

}

RACReturnSignal在被订阅的时候,就只会发送一个value值的信号,发送完毕之后就sendCompleted。

3. RACDynamicSignal

这个信号是创建RACSignal createSignal:的真身。关于RACDynamicSignal详细过程请看第一篇文章

4. RACErrorSignal

 

 

1

2

3

4

@interface RACErrorSignal : RACSignal

@property (nonatomic, strong, readonly) NSError *error;

+ (RACSignal *)error:(NSError *)error;

@end

RACErrorSignal信号里面就存储了一个NSError。

 

1

2

3

4

5

6

7

8

9

10

11

12

+ (RACSignal *)error:(NSError *)error {

    RACErrorSignal *signal = [[self alloc] init];

    signal->_error = error;

 

#ifdef DEBUG

    [signal setNameWithFormat:@"+error: %@", error];

#else

    signal.name = @"+error:";

#endif

 

    return signal;

}

RACErrorSignal初始化的时候把外界传进来的Error保存起来。当被订阅的时候就发送这个Error出去。

5. RACChannelTerminal

 

 

1

2

3

4

5

6

7

8

9

@interface RACChannelTerminal : RACSignal

 

- (id)init __attribute__((unavailable("Instantiate a RACChannel instead")));

 

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

@property (nonatomic, strong, readonly) id otherTerminal;

- (id)initWithValues:(RACSignal *)values otherTerminal:(id)otherTerminal;

 

@end

RACChannelTerminal在RAC日常开发中,用来双向绑定的。它和RACSubject一样,既继承自RACSignal,同样又遵守RACSubscriber协议。虽然具有RACSubject的发送和接收信号的特性,但是它依旧是冷信号,因为它无法一对多,它发送信号还是只能一对一。

RACChannelTerminal无法手动初始化,需要靠RACChannel去初始化。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

- (id)init {

    self = [super init];

    if (self == nil) return nil;

 

    RACReplaySubject *leadingSubject = [[RACReplaySubject replaySubjectWithCapacity:0] setNameWithFormat:@"leadingSubject"];

    RACReplaySubject *followingSubject = [[RACReplaySubject replaySubjectWithCapacity:1] setNameWithFormat:@"followingSubject"];

 

    [[leadingSubject ignoreValues] subscribe:followingSubject];

    [[followingSubject ignoreValues] subscribe:leadingSubject];

 

    _leadingTerminal = [[[RACChannelTerminal alloc] initWithValues:leadingSubject otherTerminal:followingSubject] setNameWithFormat:@"leadingTerminal"];

    _followingTerminal = [[[RACChannelTerminal alloc] initWithValues:followingSubject otherTerminal:leadingSubject] setNameWithFormat:@"followingTerminal"];

 

    return self;

}

在RACChannel的初始化中会调用RACChannelTerminal的initWithValues:方法,这里的入参都是RACReplaySubject类型的。所以订阅RACChannelTerminal过程的时候:

 

1

2

3

- (RACDisposable *)subscribe:(id)subscriber {

    return [self.values subscribe:subscriber];

}

self.values其实就是一个RACReplaySubject,就相当于订阅RACReplaySubject。订阅过程同上面RACReplaySubject的订阅过程。

 

1

2

3

4

5

6

7

8

9

10

11

- (void)sendNext:(id)value {

    [self.otherTerminal sendNext:value];

}

 

- (void)sendError:(NSError *)error {

    [self.otherTerminal sendError:error];

}

 

- (void)sendCompleted {

    [self.otherTerminal sendCompleted];

}

self.otherTerminal也是RACReplaySubject类型的,RACChannelTerminal管道两边都是RACReplaySubject类型的信号。当RACChannelTerminal开始sendNext,sendError,sendCompleted是调用的管道另外一个的RACReplaySubject进行这些对应的操作的。

平时使用RACChannelTerminal的地方在View和ViewModel的双向绑定上面。

例如在登录界面,输入密码文本框TextField和ViewModel的Password双向绑定

 

1

2

3

4

    RACChannelTerminal *passwordTerminal = [_passwordTextField rac_newTextChannel];

    RACChannelTerminal *viewModelPasswordTerminal = RACChannelTo(_viewModel, password);

    [viewModelPasswordTerminal subscribe:passwordTerminal];

    [passwordTerminal subscribe:viewModelPasswordTerminal];

双向绑定的两个信号都会因为对方的改变而收到新的信号。

至此所有的RACSignal的分类就都理顺了,按照冷信号和热信号的分类也分好了。

151194012-c465cce4a8cf87e8

四. 冷信号是如何转换成热信号的

161194012-7a1f6aaa96528c86

为何有时候需要把冷信号转换成热信号呢?详情可以看这篇文章里面举的例子:细说ReactiveCocoa的冷信号与热信号(二):为什么要区分冷热信号

根据RACSignal订阅和发送信号的流程,我们可以知道,每订阅一次冷信号RACSignal,就会执行一次didSubscribe闭包。这个时候就是可能出现问题的地方。如果RACSignal是被用于网络请求,那么在didSubscribe闭包里面会被重复的请求。上面文中提到了信号被订阅了6次,网络请求也会请求6次。这并不是我们想要的。网络请求只需要请求1次。

如何做到信号只执行一次didSubscribe闭包,最重要的一点是RACSignal冷信号只能被订阅一次。由于冷信号只能一对一,那么想一对多就只能交给热信号去处理了。这时候就需要把冷信号转换成热信号。

在ReactiveCocoa v2.5中,冷信号转换成热信号需要用到RACMulticastConnection 这个类。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

@interface RACMulticastConnection : NSObject

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

- (RACDisposable *)connect;

- (RACSignal *)autoconnect;

@end

 

 

@interface RACMulticastConnection () {

    RACSubject *_signal;

    int32_t volatile _hasConnected;

}

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

@property (strong) RACSerialDisposable *serialDisposable;

@end

看看RACMulticastConnection类的定义。最主要的是保存了两个信号,一个是RACSubject,一个是sourceSignal(RACSignal类型)。在.h中暴露给外面的是RACSignal,在.m中实际使用的是RACSubject。看它的定义就能猜到接下去它会做什么:用sourceSignal去发送信号,内部再用RACSubject去订阅sourceSignal,然后RACSubject会把sourceSignal的信号值依次发给它的订阅者们。

171194012-9df53755b12ab4e9

用一个不恰当的比喻来形容RACMulticastConnection,它就像上图中心的那个“地球”,“地球”就是订阅了sourceSignal的RACSubject,RACSubject把值发送给各个“连接”者(订阅者)。sourceSignal只有内部的RACSubject一个订阅者,所以就完成了我们只想执行didSubscribe闭包一次,但是能把值发送给各个订阅者的愿望。

在看看RACMulticastConnection的初始化

 

1

2

3

4

5

6

7

8

9

10

11

12

13

- (id)initWithSourceSignal:(RACSignal *)source subject:(RACSubject *)subject {

    NSCParameterAssert(source != nil);

    NSCParameterAssert(subject != nil);

 

    self = [super init];

    if (self == nil) return nil;

 

    _sourceSignal = source;

    _serialDisposable = [[RACSerialDisposable alloc] init];

    _signal = subject;

 

    return self;

}

初始化方法就是把外界传进来的RACSignal保存成sourceSignal,把外界传进来的RACSubject保存成自己的signal属性。

RACMulticastConnection有两个连接方法。

 

1

2

3

4

5

6

7

8

- (RACDisposable *)connect {

    BOOL shouldConnect = OSAtomicCompareAndSwap32Barrier(0, 1, &_hasConnected);

 

    if (shouldConnect) {

        self.serialDisposable.disposable = [self.sourceSignal subscribe:_signal];

    }

    return self.serialDisposable;

}

这里出现了一个不多见的函数OSAtomicCompareAndSwap32Barrier,它是原子运算的操作符,主要用于Compare and swap,原型如下:

 

1

bool    OSAtomicCompareAndSwap32Barrier( int32_t __oldValue, int32_t __newValue, volatile int32_t *__theValue );

关键字volatile只确保每次获取volatile变量时都是从内存加载变量,而不是使用寄存器里面的值,但是它不保证代码访问变量是正确的。

如果用伪代码去实现这个函数:

 

1

2

3

4

5

6

f (*__theValue == __oldValue) {  

    *__theValue = __newValue;  

    return 1;  

} else {  

    return 0;  

}

如果_hasConnected为0,意味着没有连接,OSAtomicCompareAndSwap32Barrier返回1,shouldConnect就应该连接。如果_hasConnected为1,意味着已经连接过了,OSAtomicCompareAndSwap32Barrier返回0,shouldConnect不会再次连接。

所谓连接的过程就是RACMulticastConnection内部用RACSubject订阅self.sourceSignal。sourceSignal是RACSignal,会把订阅者RACSubject保存到RACPassthroughSubscriber中,sendNext的时候就会调用RACSubject sendNext,这时就会把sourceSignal的信号都发送给各个订阅者了。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

- (RACSignal *)autoconnect {

    __block volatile int32_t subscriberCount = 0;

 

    return [[RACSignal

        createSignal:^(id subscriber) {

            OSAtomicIncrement32Barrier(&subscriberCount);

 

            RACDisposable *subscriptionDisposable = [self.signal subscribe:subscriber];

            RACDisposable *connectionDisposable = [self connect];

 

            return [RACDisposable disposableWithBlock:^{

                [subscriptionDisposable dispose];

 

                if (OSAtomicDecrement32Barrier(&subscriberCount) == 0) {

                    [connectionDisposable dispose];

                }

            }];

        }]

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

}

OSAtomicIncrement32Barrier 和 OSAtomicDecrement32Barrier也是原子运算的操作符,分别是+1和-1操作。在autoconnect为了保证线程安全,用到了一个subscriberCount的类似信号量的volatile变量,保证第一个订阅者能连接上。返回的新的信号的订阅者订阅RACSubject,RACSubject也会去订阅内部的sourceSignal。

把冷信号转换成热信号用以下5种方式,5种方法都会用到RACMulticastConnection。接下来一一分析它们的具体实现。

1. multicast:

 

 

1

2

3

4

5

- (RACMulticastConnection *)multicast:(RACSubject *)subject {

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

    RACMulticastConnection *connection = [[RACMulticastConnection alloc] initWithSourceSignal:self subject:subject];

    return connection;

}

multicast:的操作就是初始化一个RACMulticastConnection对象,SourceSignal是self,内部的RACSubject是入参subject。

 

1

2

3

4

5

    RACMulticastConnection *connection = [signal multicast:[RACSubject subject]];

    [connection.signal subscribeNext:^(id x) {

        NSLog(@"%@",x);

    }];

    [connection connect];

调用 multicast:把冷信号转换成热信号有一个点不方便的是,需要自己手动connect。注意转换完之后的热信号在RACMulticastConnection的signal属性中,所以需要订阅的是connection.signal。

2. publish

 

 

1

2

3

4

5

- (RACMulticastConnection *)publish {

    RACSubject *subject = [[RACSubject subject] setNameWithFormat:@"[%@] -publish", self.name];

    RACMulticastConnection *connection = [self multicast:subject];

    return connection;

}

publish方法只不过是去调用了multicast:方法,publish内部会新建好一个RACSubject,并把它当成入参传递给RACMulticastConnection。

 

1

2

3

4

5

    RACMulticastConnection *connection = [signal publish];

    [connection.signal subscribeNext:^(id x) {

        NSLog(@"%@",x);

    }];

    [connection connect];

同样publish方法也需要手动的调用connect方法。

3. replay

 

 

1

2

3

4

5

6

7

8

- (RACSignal *)replay {

    RACReplaySubject *subject = [[RACReplaySubject subject] setNameWithFormat:@"[%@] -replay", self.name];

 

    RACMulticastConnection *connection = [self multicast:subject];

    [connection connect];

 

    return connection.signal;

}

replay方法会把RACReplaySubject当成RACMulticastConnection的RACSubject传递进去,初始化好了RACMulticastConnection,再自动调用connect方法,返回的信号就是转换好的热信号,即RACMulticastConnection里面的RACSubject信号。

这里必须是RACReplaySubject,因为在replay方法里面先connect了。如果用RACSubject,那信号在connect之后就会通过RACSubject把原信号发送给各个订阅者了。用RACReplaySubject把信号保存起来,即使replay方法里面先connect,订阅者后订阅也是可以拿到之前的信号值的。

4. replayLast

 

 

1

2

3

4

5

6

7

8

- (RACSignal *)replayLast {

    RACReplaySubject *subject = [[RACReplaySubject replaySubjectWithCapacity:1] setNameWithFormat:@"[%@] -replayLast", self.name];

 

    RACMulticastConnection *connection = [self multicast:subject];

    [connection connect];

 

    return connection.signal;

}

replayLast 和 replay的实现基本一样,唯一的不同就是传入的RACReplaySubject的Capacity是1,意味着只能保存最新的值。所以使用replayLast,订阅之后就只能拿到原信号最新的值。

5. replayLazily

 

 

1

2

3

4

5

6

7

8

9

- (RACSignal *)replayLazily {

    RACMulticastConnection *connection = [self multicast:[RACReplaySubject subject]];

    return [[RACSignal

        defer:^{

            [connection connect];

            return connection.signal;

        }]

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

}

replayLazily 的实现也和 replayLast、replay实现很相似。只不过把connect放到了defer的操作里面去了。

defer操作的实现如下:

 

1

2

3

4

5

6

7

+ (RACSignal *)defer:(RACSignal * (^)(void))block {

    NSCParameterAssert(block != NULL);

 

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

        return [block() subscribe:subscriber];

    }] setNameWithFormat:@"+defer:"];

}

defer 单词的字面意思是延迟的。也和这个函数实现的效果是一致的。只有当defer返回的新信号被订阅的时候,才会执行入参block( )闭包。订阅者会订阅这个block( )闭包的返回值RACSignal。

block( )闭包被延迟创建RACSignal了,这就是defer。如果block( )闭包含有和时间有关的操作,或者副作用,想要延迟执行,就可以用defer。

还有一个类似的操作,then

 

1

2

3

4

5

6

7

8

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

    NSCParameterAssert(block != nil);

 

    return [[[self

        ignoreValues]

        concat:[RACSignal defer:block]]

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

}

then的操作也是延迟,只不过它是把block( )闭包延迟到原信号发送complete之后。通过then信号变化得到的新的信号,在原信号发送值的期间的时间内,都不会发送任何值,因为ignoreValues了,一旦原信号sendComplete之后,就紧接着block( )闭包产生的信号。

回到replayLazily操作上来,作用同样是把冷信号转换成热信号,只不过sourceSignal是在返回的新信号第一次被订阅的时候才被订阅。原因就是defer延迟了block( )闭包的执行了。

 

 

 


 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值