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的方法。
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,所以它也能调用这个方法。
RACSignal的block就这样被保存起来了,那什么时候会被执行呢?
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; } |
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。
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也都是同理。
总结一下:
- RACSignal调用subscribeNext方法,新建一个RACSubscriber。
- 新建的RACSubscriber会copy,nextBlock,errorBlock,completedBlock存在自己的属性变量中。
- RACSignal的子类RACDynamicSignal调用subscribe方法。
- 新建RACCompoundDisposable和RACPassthroughSubscriber对象。RACPassthroughSubscriber分别保存对RACSignal,RACSubscriber,RACCompoundDisposable的引用,注意对RACSignal的引用是unsafe_unretained的。
- RACDynamicSignal调用didSubscribe闭包。先调用RACPassthroughSubscriber的相应的sendNext,sendError,sendCompleted方法。
- RACPassthroughSubscriber再去调用self.innerSubscriber,即RACSubscriber的nextBlock,errorBlock,completedBlock。注意这里调用同样是先copy一份,再调用闭包执行。
三. RACSignal操作的核心bind实现
在RACSignal的源码里面包含了两个基本操作,concat和zipWith。不过在分析这两个操作之前,先来分析一下更加核心的一个函数,bind操作。
先来说说bind函数的作用:
- 会订阅原始的信号。
- 任何时刻原始信号发送一个值,都会绑定的block转换一次。
- 一旦绑定的block转换了值变成信号,就立即订阅,并把值发给订阅者subscriber。
- 一旦绑定的block要终止绑定,原始的信号就complete。
- 当所有的信号都complete,发送completed信号给订阅者subscriber。
- 如果中途信号出现了任何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的时候,流程如下:
- bindSignal执行didSubscribe的block,即执行block6。
- 在block6 的第一句代码,就是调用RACStreamBindBlock bindingBlock = block(),这里的block是外面传进来的block2,于是开始调用block2。执行完block2,会返回一个RACStreamBindBlock的对象。
- 由于是signal调用的bind函数,所以bind函数里面的self就是signal,在bind内部订阅了signal的信号。subscribeNext所以会执行block1。
- 执行block1,sendNext调用订阅者subscriber的nextBlock,于是开始执行block10。
- block10中会先调用bindingBlock,这个是之前调用block2的返回值,这个RACStreamBindBlock对象里面保存的是block3。所以开始调用block3。
- 在block3中入参是一个value,这个value是signal中sendNext中发出来的value的值,在block3中可以对value进行变换,变换完成后,返回一个新的信号signal’。
- 如果返回的signal’为空,则会调用completeSignal,即调用block7。block7中会发送sendCompleted。如果返回的signal’不为空,则会调用addSignal,即调用block8。block8中会继续订阅signal’。执行block9。
- block9 中会sendNext,这里的subscriber是block6的入参,于是对subscriber调用sendNext,会调用到bindSignal的订阅者的block4中。
- block9 中执行完sendNext,还会调用sendCompleted。这里的是在执行block9里面的completed闭包。completeSignal(signal, selfDisposable);然后又会调用completeSignal,即block7。
- 执行完block7,就完成了一次从signal 发送信号sendNext的全过程。
bind整个流程就完成了。
四. RACSignal基本操作concat和zipWith实现
接下来再来分析RACSignal中另外2个基本操作。
1. concat
写出测试代码:
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操作就是把两个信号合并起来。注意合并有先后顺序。
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起来。
当合并之后的信号被订阅的时候:
- 调用新的合并信号的didSubscribe。
- 由于是第一个信号调用的concat方法,所以block中的self是前一个信号signal。合并信号的didSubscribe会先订阅signal。
- 由于订阅了signal,于是开始执行signal的didSubscribe,sendNext,sendError。
- 当前一个信号signal发送sendCompleted之后,就会开始订阅后一个信号signals,调用signals的didSubscribe。
- 由于订阅了后一个信号,于是后一个信号signals开始发送sendNext,sendError,sendCompleted。
这样两个信号就前后有序的拼接到了一起。
这里有二点需要注意的是:
- 只有当第一个信号完成之后才能收到第二个信号的值,因为第二个信号是在第一个信号completed的闭包里面订阅的,所以第一个信号不结束,第二个信号也不会被订阅。
- 两个信号concat在一起之后,新的信号的结束信号在第二个信号结束的时候才结束。看上图描述,新的信号的发送长度等于前面两个信号长度之和,concat之后的新信号的结束信号也就是第二个信号的结束信号。
2. zipWith
写出测试代码:
1 2 3 4 5 | RACSignal *concatSignal = [signal zipWith:signals];
[concatSignal subscribeNext:^(id x) { NSLog(@"subscribe value = %@", x); }]; |
源码如下:
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里面有两个数组,分别会存储两个信号的值。
- 一旦订阅了zipWith之后的信号,就开始执行didSubscribe闭包。
- 在闭包中会先订阅第一个信号。这里假设第一个信号比第二个信号先发出一个值。第一个信号发出来的每一个值都会被加入到第一个数组中保存起来,然后调用sendNext( )闭包。在sendNext( )闭包中,会先判断两个数组里面是否都为空,如果有一个数组里面是空的,就return。由于第二个信号还没有发送值,即第二个信号的数组里面是空的,所以这里第一个值发送不出来。于是第一个信号被订阅之后,发送的值存储到了第一个数组里面了,没有发出去。
- 第二个信号的值紧接着发出来了,第二个信号每发送一次值,也会存储到第二个数组中,但是这个时候再调用sendNext( )闭包的时候,不会再return了,因为两个数组里面都有值了,两个数组的第0号位置都有一个值了。有值以后就打包成元组RACTuple发送出去。并清空两个数组0号位置存储的值。
- 以后两个信号每次发送一个,就先存储在数组中,只要有“配对”的另一个信号,就一起打包成元组RACTuple发送出去。从图中也可以看出,zipWith之后的新信号,每个信号的发送时刻是等于两个信号最晚发出信号的时刻。
- 新信号的完成时间,是当两者任意一个信号完成并且数组里面为空,就算完成了。所以最后第一个信号发送的5的那个值就被丢弃了。
第一个信号依次发送的1,2,3,4的值和第二个信号依次发送的A,B,C,D的值,一一的合在了一起,就像拉链把他们拉在一起。由于5没法配对,所以拉链也拉不上了。
目录
- 1.关于冷信号和热信号的概念
- 2.RACSignal热信号
- 3.RACSignal冷信号
- 4.冷信号是如何转换成热信号的
一. 关于冷信号和热信号的概念
冷热信号的概念是源自于源于.NET框架Reactive Extensions(RX)中的Hot Observable和Cold Observable,
Hot Observable是主动的,尽管你并没有订阅事件,但是它会时刻推送,就像鼠标移动;而Cold Observable是被动的,只有当你订阅的时候,它才会发布消息。
Hot Observable可以有多个订阅者,是一对多,集合可以与订阅者共享信息;而Cold Observable只能一对一,当有不同的订阅者,消息是重新完整发送。
在这篇文章细说ReactiveCocoa的冷信号与热信号(一)详细分析了冷热信号的特点:
热信号是主动的,即使你没有订阅事件,它仍然会时刻推送。而冷信号是被动的,只有当你订阅的时候,它才会发送消息。
热信号可以有多个订阅者,是一对多,信号可以与订阅者共享信息。而冷信号只能一对一,当有不同的订阅者,消息会从新完整发送。
二. RACSignal热信号
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冷信号
在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的分类就都理顺了,按照冷信号和热信号的分类也分好了。
四. 冷信号是如何转换成热信号的
为何有时候需要把冷信号转换成热信号呢?详情可以看这篇文章里面举的例子:细说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的信号值依次发给它的订阅者们。
用一个不恰当的比喻来形容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( )闭包的执行了。