本文基于Reactive 3.1.0
目录
1. RACSignal的定义
2. RACSignal与RACStream的关系
3. RACSignal是如何发送信号的?
4. RACSignal的扩展
一、 RACSignal的定义
ReactiveCocoa 将原有的Delegate,Block Callback,Target-Action,Timers,KVO都统一抽象成了RACSignal的消息处理机制,在ReactiveCocoa中, 核心类就是RACSignal, 所有的消息都是通过信号的方式进行传递,可以简单的理解这些信号就是一连串的状态,在状态发生改变的时候,对应的订阅者就会收到通知,然后执行相应的操作。 因此RAC的整个框架也都是围绕着RACSignal的概念进行组织的。
二、RACSignal与RACStream的关系
RACSignal 继承自RACStream, 同样继承自RACStream的还有另一个类:RACSequence(RACSequence 是 pull-driven的数据流, RACSignal 是push-driven的数据流, RACSequence 不在本文讨论范围之内)
RACStream 本身作为一个抽象类并不提供任何实现,RACStream本身也提供了一些抽象方法,不可以直接调用,直接调用的话会抛出异常。
在看上述方法的实现之前,先要讲一下RACSignal类簇
某些抽象方法指定了到了对应的子类里面,而这些子类也只是简单的对值进行存储,然后在订阅者订阅的时候,将值发送出去,不同的是,RACEmptySignal发送的是空值,RACErrorSignal发送的是error,RACReturnSignal发送的value, 以RACErrorSignal为例:
+ (RACSignal *)error:(NSError *)error {
RACErrorSignal *signal = [[self alloc] init];
signal->_error = error;
return signal
}
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
return [RACScheduler.subscriptionScheduler schedule:^{
[subscriber sendError:self.error];
}];
}
复制代码
然后讲上述剩余抽象方法的实现:
bind (RACSignal的核心方法)
- (RACSignal *)bind:(RACSignalBindBlock (^)(void))block {
NSCParameterAssert(block != NULL);
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
RACSignalBindBlock bindingBlock = block();
__block volatile int32_t signalCount = 1; // indicates self
RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable];
void (^completeSignal)(RACDisposable *) = ^(RACDisposable *finishedDisposable) {
if (OSAtomicDecrement32Barrier(&signalCount) == 0) {
[subscriber sendCompleted];
[compoundDisposable dispose];
} else {
[compoundDisposable removeDisposable:finishedDisposable];
}
};
void (^addSignal)(RACSignal *) = ^(RACSignal *signal) {
OSAtomicIncrement32Barrier(&signalCount);
RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init];
[compoundDisposable addDisposable:selfDisposable];
RACDisposable *disposable = [signal subscribeNext:^(id x) {
[subscriber sendNext:x];
} error:^(NSError *error) {
[compoundDisposable dispose];
[subscriber sendError:error];
} completed:^{
@autoreleasepool {
completeSignal(selfDisposable);
}
}];
selfDisposable.disposable = disposable;
};
@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(selfDisposable);
}
}
} error:^(NSError *error) {
[compoundDisposable dispose];
[subscriber sendError:error];
} completed:^{
@autoreleasepool {
completeSignal(selfDisposable);
}
}];
selfDisposable.disposable = bindingDisposable;
}
return compoundDisposable;
}] setNameWithFormat:@"[%@] -bind:", self.name];
}
复制代码
bind的实现相对于其他抽象方法的实现较为复杂:
bind方法内部主要做了这么几件事情:
-
首先根据传入的block生成一个RACSignalBindBlock(返回值是RACSignal对象)类型的bindingBlock
-
声明2个block,一个是completeSignal,一个是addSignal,completeSignal用于新的signal send completed,addSignal用于sendNext、sendError、调用completeSignal。这里值得注意的是会在一开始声明一个int32_t类型的signalCount,在addSignalBlock中对signalCount执行了一下OSAtomicIncrement32Barrier原子操作,对signalCount进行+1,同时在completeSignalBlock里面进行减一操作,目的是防止bindSignal进行completed操作,而不是originSignal的sendCompleted操作而导致的completed操作。
-
订阅originSignal,一旦绑定的block转变成signal且不为空,且compoundDisposable没有被dispose,执行2中所说的addSignal,立即将值发送给订阅者subscriber,如果转变的signal为空或者要终止绑定,原始的信号就complete,当所有的信号都complete,发送completed信号给订阅者subscriber,如果中途信号出现了任何error,都会把这个错误发送给subscriber
在省略掉部分判断以及RACDisposable的处理之后,看bind的实现,其实是对原有的Signal进行了2次封装: 当原来的Signal发送消息之后,RACSignalBindBlock拿到原有Signal发送的信息,然后进行block处理,返回封装之后的signal, 而且不难发现,每一次的消息发送都会被包成一个signal,新的signal再进行消息的发送。 绑定之后的signal不会影响原有signal的订阅操作,类似于category操作。
example:
- (void)testBind
{
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@1];
[subscriber sendNext:@2];
[subscriber sendNext:@3];
[subscriber sendNext:@4];
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^{
NSLog(@"_______dispose");
}];
}];
RACSignal *bindSignal = [signal bind:^RACSignalBindBlock _Nonnull{
return ^RACSignal *(NSNumber *value, BOOL *stop) {
value = @(value.integerValue * value.integerValue);
if (value.integerValue > 5) {
*stop = YES;
}
return [RACSignal return:value];
};
}];
[signal subscribeNext:^(NSNumber * _Nullable x) {
NSLog(@"signal subsbribeNext : %ld", x.integerValue);
} completed:^{
NSLog(@"signal subsbribeCompleted");
}];
[bindSignal subscribeNext:^(NSNumber *_Nullable x) {
NSLog(@"bindSignal subsbribeNext : %ld", x.integerValue);
} completed:^{
NSLog(@"bindSignal subsbribeCompleted");
}];
}
复制代码
concat
- (RACSignal *)concat:(RACSignal *)signal {
......
RACDisposable *sourceDisposable = [self subscribeNext:^(id x) {
......
} error:^(NSError *error) {
......
} completed:^{
RACDisposable *concattedDisposable = [signal subscribe:subscriber];
[compoundDisposable addDisposable:concattedDisposable];
}];
......
}]];
}
复制代码
调用concat之后的signal的didSubscribe, 会先订阅前一个signal,并正常的执行前一个signal的didSubscribe,当前一个signal sendCompleted的时候,就开始订阅后一个signal,然后开始执行后一个signal的didSubscribe, 在concat之前,前后的signal会首先将各自的didSubscribe copy起来,然后在concat之后,新的signal的didSubscribe 再把对应的block copy。
值得注意的是:后一个signal是在前一个signal sendCompleted之后订阅的,那么如果前一个信号没有sendCompleted,后一个信号是不会被订阅的,因此concat是一个有序的signal组合,concat得到的新的signal能收到两个signal发送的消息值。
example:
- (void)testConcat
{
RACSignal *firstSignal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"1"];
[subscriber sendNext:@"1"];
[subscriber sendNext:@"1"];
[subscriber sendCompleted];
return nil;
}];
RACSignal *twoSignal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"2"];
[subscriber sendNext:@"2"];
[subscriber sendCompleted];
return nil;
}];
RACSignal *concatSignal = [firstSignal concat:twoSignal];
[concatSignal subscribeNext:^(NSString * _Nullable x) {
NSLog(@"-----concat: %@", x);
}];
}
复制代码
zipWith
简化一下zipWith的实现代码:
- (RACSignal *)zipWith:(RACSignal *)signal {
BOOL selfCompleted、otherCompleted = NO, NSMutableArray selfValues、OtherValues;
void (^sendCompleted) = ^{selfEmpty = (selfCompleted && selfValues.count == 0) otherEmpty = (otherCompleted && otherValues.count == 0); if(selfEmpty || otherEmpty) sendCompleted}
void (^sendNext) = ^{
if (selfValues.count == 0) return; if (otherValues.count == 0) return;
RACTuple *tuple = RACTuplePack(selfValues[0], otherValues[0],
[selfValues removeObjectAtIndex:0];[otherValues removeObjectAtIndex:0];
sendNext(tuple))};
......
[self subscribeNext:^(id x){
......
[selfValues add: x]; sendNext()
......
} completed:^{selfCompleted = Yes, sendCompleted()}];
[other 如上];
}
复制代码
调用zipWith之后的signal的didSubscribe,两个signal会各自发送值,当第一个signal发送消息的时候,selfValues数组将值保存下来,并调用sendNext回调,sendNext里面会判断两个数组是否为空,有一个为空,则会return,在completed回调里面会将selfCompleted标志位置为Yes,并调用sendCompleted回调,在回调里面,同样也会判断标记位,但同时也会判断数组是否为空,因此这个判断条件的满足只有两个signal都发送消息一一配对发送出去才会走进,因为只有在sendNext回调里面,将两个数组的第一个元素取出来并打包成元祖RACTuple发送出去,并清空数组里面的第一个元素。
因为两个signal每次发送消息的时候,对应的数组都会先将值保存下来,只有在另一个signal也发送消息时,才会打包成RACTuple发送出去,也就是说,如果其中一个signal发送了消息,但是另一个signal一直没有发送消息,那么第一个signal发送的消息永远不会被zipWith之后的signal发送,这个值就没有意义啦,需要一一配对。
example:
- (void)testZipWith
{
RACSignal *firstSignal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"1"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subscriber sendNext:@"2"];
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subscriber sendNext:@"3"];
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subscriber sendNext:@"4"];
[subscriber sendCompleted];
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subscriber sendNext:@"5"];
[subscriber sendCompleted];
});
return nil;
}];
RACSignal *secondSignal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"A"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subscriber sendNext:@"B"];
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subscriber sendNext:@"C"];
[subscriber sendCompleted];
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subscriber sendNext:@"D"];
[subscriber sendCompleted];
});
return nil;
}];
RACSignal *zipSignal = [firstSignal zipWith:secondSignal];
[zipSignal subscribeNext:^(id _Nullable x) {
NSLog(@"zipWith subscribeNext : %@", x);
} completed:^{
NSLog(@"zipWith subscribeCompleted");
}];
}
复制代码
那么到现在RACStream的几个抽象方法在RACSignal的实现就讲完啦。
三、RACSignal是如何发送信号的?
一个正常的信号工作处理过程是这样的:
整个流程是在信号创建之后调用subscirbeNext之后返回一个RACDisposable对象,然后在整个订阅过程中,生成了一个RACSubscriber对象,当向这个对象sendNext或者sendCompleted的时候,它就会向所有的订阅者发送一个消息,其实不难看出,整个的订阅和发送过程都和RACSubscriber有关,那么就来深入探究一下整个的订阅与发送过程。
RACSubscriber:
RACSubscriber的初始化是由nextBlock、errorBlock、completedBlock三个参数初始化的,那么当调用方去使用如下方法时:
-subscribeNext、 -subscribeNext:error: -subscribeNext:completed: 等等方法时,其实也只是上面的三个block的组合,不需要的block传NULL,看一下最长初始化的实现:
+ (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;
}
复制代码
这个初始化只是将三个block保存下来,在拿到RACSubscribe实例之后,会调用subscribe:方法,这个方法在RACSignal是一个抽象方法,需要RACSignal的类簇去实现:
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
NSCAssert(NO, @"This method must be overridden by subclasses");
return nil;
}
复制代码
RACErrorSignal、RACEmptySignal、RACReturnSignal的实现都是类似的,比较简单,在RACSubscriptionScheduler中schedule闭包中执行block,在常规工作流程中正常调用的RACDynamicSignal类的subscribe: 方法:
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
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;
}
复制代码
看这段代码:
在这个方法里面首先创建一个RACCompoundDisposable实例。
RACDisposable
RACCompoundDisposable虽然是RACDisposable的子类,但是可以加入多个RACDisposable实例,在执行RACCompoundDisposable的dispose方法,可以将加入的多个RACDisposable实例dispose,当RACCompoundDisposable被dispose的话,也会将容器内的所有RACDisposable实例dispose掉。然后又通过RACSubscribe、RACSignal、RACDisposable实例参数创建了一个RACPassthroughSubscriber实例,他的作用就是将所有的信号从一个RACSubscribe实例传递给内部的RACSubscribe,但其实看到内部发现,内部的RACSubscribe也是外部传入的。传入的RACCompoundDisposable作用是当这个实例被dispose的时候,内部的subscribe将收不到任何消息。
if (RACSIGNAL_NEXT_ENABLED()) {
RACSIGNAL_NEXT(cleanedSignalDescription(self.signal), cleanedDTraceString(self.innerSubscriber.description), cleanedDTraceString([value description]));
}
复制代码
值得注意的是:RACPassthroughSubscriber初始化的时候传入了一个RACSignal,为了避免循环引用,使用了unsafe_unretained来持有这个signal,那么为什么不用weak呢,因为在这里用到RACSignal只有用来作为DTTrace 动态跟踪的一个探针,没必要使用weak,来做额外的性能消耗。
由此信号的订阅就讲完啦, 信号是如何发送的呢?
- (void)sendNext:(id)value {
@synchronized (self) {
void (^nextBlock)(id) = [self.next copy];
if (nextBlock == nil) return;
nextBlock(value);
}
}
复制代码
看信号的发送你会发现十分敷衍 精简,将初始化的nextBlock、或者errorBlock copy一份,进行调用,copy的过程也是线程安全的,至此RACSignal的订阅与发送过程也讲完啦。
四、RACSignal的扩展
下面讲一下RACSignal的一些高级用法的源码实现:
flattenMap & flatten
- (RACStream *)flattenMap:(__kindof RACStream * (^)(id value))block {
......
return [[self bind:^{
.....
id stream = block(value) ?: [class empty];
NSCAssert([stream isKindOfClass:RACStream.class], @"Value returned from -flattenMap: is not a stream: %@", stream);
return stream;
};
}] .....];
}
- (RACStream *)flatten {
return [[self flattenMap:^(id value) {
return value;
}] ......];
}
复制代码
flattenMap的实现是放在其父类RACStream中的,但其实内部是调用了bind方法,对原信号进行block转换之后变成新的信号,flatten其实是对高阶信号(信号的信号)的一次降阶,如果不是高阶信号,那么就会命中Assert。
那么写一个Example:
- (void)testFlattenMap
{
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@1];
[subscriber sendNext:@2];
[subscriber sendNext:@3];
[subscriber sendCompleted];
return nil;
}];
RACSignal *flattenSignal = [signal flattenMap:^__kindof RACSignal * _Nullable(NSNumber * _Nullable value) {
return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@(value.integerValue * value.integerValue)];
[subscriber sendCompleted];
return nil;
}];
}];
RACSignal *mapSignal = [signal map:^id _Nullable(NSNumber * _Nullable value) {
return @(value.integerValue * 2);
}];
[flattenSignal subscribeNext:^(NSNumber * _Nullable x) {
NSLog(@"flattenMap Result : %ld", x.integerValue);
}];
[mapSignal subscribeNext:^(NSNumber * _Nullable x) {
NSLog(@"map Result : %ld", x.integerValue);
}];
}
复制代码
看一下输出结果:
map & filter & mapReduce
- (RACStream *)map:(id (^)(id value))block {
Class class = self.class;
return [[self flattenMap:^(id value) {
return [class return:block(value)];
}] ......];
}
- (RACStream *)filter:(BOOL (^)(id value))block {
Class class = self.class;
return [[self flattenMap:^ id (id value) {
if (block(value)) {
return [class return:value];
} else {
return class.empty;
}
}] ......];
}
- (__kindof RACStream *)mapReplace:(id)object {
return [[self map:^(id _) {
return object;
}] ......];
}
复制代码
map的实现是进行了一次flattenMap,对新的信号的值进行block(value),比较简单. filter的实现和map的实现类似,但是会把block的返回值作为判断条件,不满足则会返回空的signal。 mapReplace的操作是不管原先的signal发送什么消息,都会统一替换成object。(感觉这个需求会比较少)。
merge
+ (RACSignal *)merge:(id<NSFastEnumeration>)signals {
......
for (RACSignal *signal in signals) {
[copiedSignals addObject:signal];
}
return [[[RACSignal
createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
for (RACSignal *signal in copiedSignals) {
[subscriber sendNext:signal];
}
[subscriber sendCompleted];
return nil;
}]flatten]; ......
}
复制代码
merge的操作接受参数是一个signal的数组,内部会创建一个copiedSignals的数组,然后依次发送这个数组中的信号,由于新的信号也是一个高阶信号,需要flatten操作,将flatten操作之后的值发送出去。
副作用相关:
doNext、doError、doCompleted、initially、finally
- (RACSignal *)doNext:(void (^)(id x))block {
......
return [self subscribeNext:^(id x) {
block(x);
[subscriber sendNext:x];
}......
}
- (RACSignal *)finally:(void (^)(void))block {
return [[[self
doError:^(NSError *error) {
block();
}]
doCompleted:^{
block();
}]
......];
}
复制代码
信号的副作用操作最好放在这里面,比如说一些业务埋点,这样让读代码的人一目了然,看doNext、doError、doCompleted的源码,是在sendNext、sendError、sendCompleted之前执行block闭包,initially是在信号发送之前,执行defer操作,然后再return self之前执行一段block操作,在里面进行副作用操作,finally在doError和doCompleted插入一段block,然后进行相应的副作用操作。
多线程相关:
deliverOn
- (RACSignal *)deliverOn:(RACScheduler *)scheduler {
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
[scheduler schedule:^{
[subscriber sendNext:x];
}];
......
}
复制代码
当信号去sendNext、sendError等的时候,会先执行RACScheduler 的schedule方法,看一下schedule方法的实现:
- (RACDisposable *)schedule:(void (^)(void))block {
if (RACScheduler.currentScheduler == nil) return [self.backgroundScheduler schedule:block];
block();
return nil;
}
复制代码
首先判断currentScheduler是不是nil,则在backgroundScheduler执行block,否则在currentScheduler执行block,那么如何判断的currentScheduler是否为nil呢?
+ (RACScheduler *)currentScheduler {
RACScheduler *scheduler = NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey];
if (scheduler != nil) return scheduler;
if ([self.class isOnMainThread]) return RACScheduler.mainThreadScheduler;
return nil;
}
复制代码
是看线程字典threadDictionary有没有RACSchedulerCurrentSchedulerKey对应的value,如果有的话,返回scheduler,没有则看是否在主线程,如果在主线程,那么返回主线程scheduler,否则返回nil。
subscribeOn
- (RACSignal *)subscribeOn:(RACScheduler *)scheduler {
return [[RACSignal createSignal:^(id<RACSubscriber> 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与deliverOn明显的不同是 subscribeOn 能够保证didSubscribe block()是在哪个scheduler执行,而deliverOn是保证sendNext、sendError、sendCompleted在哪个scheduler执行。
RACSignal的高级操作非常之多,在这里就不会再一一列举。
总结
在这里只是对RACSignal的简单了解,在RAC的世界里如沧海一粟,希望能继续在状态驱动的世界里畅游。 这有一个神奇的网站:各个操作动画展示 可以看到各个操作的动画的管道流。