首先先来看看比较短的那个初始化方法。
- (id)initWithSignalBlock:(RACSignal * (^)(id input))signalBlock {
return [self initWithEnabled:nil signalBlock:signalBlock];
}复制代码
initWithSignalBlock:方法实际就是调用了initWithEnabled: signalBlock:方法。
- (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(id input))signalBlock {
}复制代码
initWithSignalBlock:方法相当于第一个参数传的是nil的initWithEnabled: signalBlock:方法。第一个参数是enabledSignal,第二个参数是signalBlock的闭包。enabledSignal如果传的是nil,那么就相当于是传进了[RACSignal return:@YES]。
接下来详细分析一下initWithEnabled: signalBlock:方法的实现。
这个方法的实现非常长,需要分段来分析。RACCommand的初始化就是对自己的4个信号,executionSignals,executing,enabled,errors的初始化。
1. executionSignals信号的初始化
RACSignal *newActiveExecutionSignals = [[[[[self rac_valuesAndChangesForKeyPath:@keypath(self.activeExecutionSignals) options:NSKeyValueObservingOptionNew observer:nil]
reduceEach:^(id _, NSDictionary *change) {
NSArray *signals = change[NSKeyValueChangeNewKey];
if (signals == nil) return [RACSignal empty];
return [signals.rac_sequence signalWithScheduler:RACScheduler.immediateScheduler];
}]
concat]
publish]
autoconnect];复制代码
通过rac_valuesAndChangesForKeyPath: options: observer: 方法监听self.activeExecutionSignals数组里面是否有增加新的信号。rac_valuesAndChangesForKeyPath: options: observer: 方法的返回时是一个RACTuple,它的定义是这样的:RACTuplePack(value, change)。
只要每次数组里面加入了新的信号,那么rac_valuesAndChangesForKeyPath: options: observer: 方法就会把新加的值和change字典包装成RACTuple返回。再对这个信号进行一次reduceEach:操作。
举个例子,change字典可能是如下的样子:
{
indexes = "<_NSCachedIndexSet: 0x60000023b8a0>[number of indexes: 1 (in 1 ranges), indexes: (0)]";
kind = 2;
new = (
"<RACReplaySubject: 0x6000006613c0> name: "
);
}复制代码
取出change[NSKeyValueChangeNewKey]就能取出每次变化新增的信号数组,然后把这个数组通过signalWithScheduler:转换成信号。
把原信号中每个值是里面装满RACTuple的信号通过变换,变换成了装满RACSingnal的三阶信号,通过concat进行降阶操作,降阶成了二阶信号。最后通过publish和autoconnect操作,把冷信号转换成热信号。
newActiveExecutionSignals最终是一个二阶热信号。
接下来再看看executionSignals是如何变换而来的。
_executionSignals = [[[newActiveExecutionSignals
map:^(RACSignal *signal) {
return [signal catchTo:[RACSignal empty]];
}]
deliverOn:RACScheduler.mainThreadScheduler]
setNameWithFormat:@"%@ -executionSignals", self];复制代码
executionSignals把newActiveExecutionSignals中错误信号都换成空信号。经过map变换之后,executionSignals是newActiveExecutionSignals的无错误信号的版本。由于map只是变换并没有降阶,所以executionSignals还是一个二阶的高阶冷信号。
注意最后加上了deliverOn,executionSignals信号每个值都是在主线程中发送的。
2. errors信号的初始化
在RACCommand中会搜集其所有的error信号,都装进自己的errors的信号中。这也是RACCommand的特点之一,能把错误统一处理。
RACMulticastConnection *errorsConnection = [[[newActiveExecutionSignals
flattenMap:^(RACSignal *signal) {
return [[signal ignoreValues]
catch:^(NSError *error) {
return [RACSignal return:error];
}];
}]
deliverOn:RACScheduler.mainThreadScheduler]
publish];复制代码
从上面分析中,我们知道,newActiveExecutionSignals最终是一个二阶热信号。这里在errorsConnection的变换中,我们对这个二阶的热信号进行flattenMap:降阶操作,只留下所有的错误信号,最后把所有的错误信号都装在一个低阶的信号中,这个信号中每个值都是一个error。同样,变换中也追加了deliverOn:操作,回到主线程中去操作。最后把这个冷信号转换成热信号,但是注意,还没有connect。
_errors = [errorsConnection.signal setNameWithFormat:@"%@ -errors", self];
[errorsConnection connect];复制代码
假设某个订阅者在RACCommand中的信号已经开始执行之后才订阅的,如果错误信号是一个冷信号,那么订阅之前的错误就接收不到了。所以错误应该是一个热信号,不管什么时候订阅都可以接收到所有的错误。
error信号就是热信号errorsConnection传出来的一个热信号。error信号每个值都是在主线程上发送的。
3. executing信号的初始化
executing这个信号表示了当前RACCommand是否在执行,信号里面的值都是BOOL类型的。那么如何拿到这样一个BOOL信号呢?
RACSignal *immediateExecuting = [RACObserve(self, activeExecutionSignals) map:^(NSArray *activeSignals) {
return @(activeSignals.count > 0);
}];复制代码
由于self.activeExecutionSignals是可以被KVO的,所以每当activeExecutionSignals变化的时候,判断当前数组里面是否还有信号,如果数组里面有值,就代表了当前有在执行中的信号。
_executing = [[[[[immediateExecuting
deliverOn:RACScheduler.mainThreadScheduler]
startWith:@NO]
distinctUntilChanged]
replayLast]
setNameWithFormat:@"%@ -executing", self];复制代码
immediateExecuting信号表示当前是否有信号在执行。初始值为NO,一旦immediateExecuting不为NO的时候就会发出信号。最后通过replayLast转换成永远只保存最新的一个值的热信号。
executing信号除去第一个默认值NO,其他的每个值也是在主线程中发送的。
4. enabled信号的初始化
RACSignal *moreExecutionsAllowed = [RACSignal
if:RACObserve(self, allowsConcurrentExecution)
then:[RACSignal return:@YES]
else:[immediateExecuting not]];复制代码
先监听self.allowsConcurrentExecution变量是否有变化,allowsConcurrentExecution默认值为NO。如果有变化,allowsConcurrentExecution为YES,就说明允许并发执行,那么就返回YES的RACSignal,allowsConcurrentExecution为NO,就说明不允许并发执行,那么就要看当前是否有正在执行的信号。immediateExecuting就是代表当前是否有在执行的信号,对这个信号取非,就是是否允许执行下一个信号的BOOL值。这就是moreExecutionsAllowed的信号。
if (enabledSignal == nil) {
enabledSignal = [RACSignal return:@YES];
} else {
enabledSignal = [[[enabledSignal
startWith:@YES]
takeUntil:self.rac_willDeallocSignal]
replayLast];
}复制代码
这里的代码就说明了,如果第一个参数传的是nil,那么就相当于传进来了一个[RACSignal return:@YES]信号。
如果enabledSignal不为nil,就在enabledSignal信号前面插入一个YES的信号,目的是为了防止传入的enabledSignal虽然不为nil,但是里面是没有信号的,比如[RACSignal never],[RACSignal empty],这些信号传进来也相当于是没用的,所以在开头加一个YES的初始值信号。
最后同样通过replayLast操作转换成只保存最新的一个值的热信号。
_immediateEnabled = [[RACSignal
combineLatest:@[ enabledSignal, moreExecutionsAllowed ]]
and];复制代码
这里涉及到了combineLatest:的变换操作,这个操作在之前的文章里面分析过了,这里不再详细分析源码实现。combineLatest:的作用就是把后面数组里面传入的每个信号,不管是谁发送出来一个信号,都会把数组里面所有信号的最新的值组合到一个RACTuple里面。immediateEnabled会把每个RACTuple里面的元素都进行逻辑and运算,这样immediateEnabled信号里面装的也都是BOOL值了。
immediateEnabled信号的意义就是每时每刻监听RACCommand是否可以enabled。它是由2个信号进行and操作得来的。每当allowsConcurrentExecution变化的时候就会产生一个信号,此时再加上enabledSignal信号,就能判断这一刻RACCommand是否能够enabled。每当enabledSignal变化的时候也会产生一个信号,再加上allowsConcurrentExecution是否允许并发,也能判断这一刻RACCommand是否能够enabled。所以immediateEnabled是由这两个信号combineLatest:之后再进行and操作得来的。
_enabled = [[[[[self.immediateEnabled
take:1]
concat:[[self.immediateEnabled skip:1] deliverOn:RACScheduler.mainThreadScheduler]]
distinctUntilChanged]
replayLast]
setNameWithFormat:@"%@ -enabled", self];复制代码
由上面源码可以知道,self.immediateEnabled是由enabledSignal, moreExecutionsAllowed组合而成的。根据源码,enabledSignal的第一个信号值一定是[RACSignal return:@YES],moreExecutionsAllowed是RACObserve(self, allowsConcurrentExecution)产生的,由于allowsConcurrentExecution默认值是NO,所以moreExecutionsAllowed的第一个值是[immediateExecuting not]。
这里比较奇怪的地方是为何要用一次concat操作,把第一个信号值和后面的连接起来。如果直接写[self.immediateEnabled deliverOn:RACScheduler.mainThreadScheduler],那么整个self.immediateEnabled就都在主线程上了。作者既然没有这么写,肯定是有原因的。
This signal will send its current value upon subscription, and then all future values on the main thread.
通过查看文档,明白了作者的意图,作者的目的是为了让第一个值以后的每个值都发送在主线程上,所以这里skip:1之后接着deliverOn:RACScheduler.mainThreadScheduler。那第一个值呢?第一个值在一订阅的时候就发送出去了,同订阅者所在线程一致。
distinctUntilChanged保证enabled信号每次状态变化的时候只取到一个状态值。最后调用replayLast转换成只保存最新值的热信号。
从源码上看,enabled信号除去第一个值以外的每个值也都是在主线程上发送的。
三. execute:底层实现分析