问题
使用RACCommand
去执行操作,造成方法执行延迟,出现launch之后,黑屏闪动的情况.
原因:
RAC内部通过dispatch_async(dispatch_get_main_queue()
实现
RAC内部对于dispatch的使用:哪些使用了dispatch
RACQueueScheduler
在RACQueueScheduler
内部实现了- (RACDisposable *)schedule:(void (^)(void))block
内部实现
- (RACDisposable *)schedule:(void (^)(void))block {
NSCParameterAssert(block != NULL);
RACDisposable *disposable = [[RACDisposable alloc] init];
dispatch_async(self.queue, ^{
if (disposable.disposed) return;
[self performAsCurrentScheduler:block];
});
return disposable;
}
RACTargetQueueScheduler
继承自RACQueueScheduler
,RACTargetQueueScheduler
的使用包括
mainThreadScheduler的初始化
RACCommand
内部都通过deliverOn:RACScheduler.mainThreadScheduler
进行了处理
RACSignal
的asynchronousFirstOrDefault
内部也切换到了mainThread
// We subscribe to the signal on the main thread so that it occurs _after_
// -addActiveExecutionSignal: completes below.
//
// This means that `executing` and `enabled` will send updated values before
// the signal actually starts performing work.
RACMulticastConnection *connection = [[signal
subscribeOn:RACScheduler.mainThreadScheduler]
multicast:[RACReplaySubject subject]];
@weakify(self);
[self addActiveExecutionSignal:connection.signal];
在上面有一段注释,是说这样通过connection将信号在主线程订阅的目的是为了,在信号真正执行前,executing和enabled信号可以发送信息.在最终的信号被订阅者订阅之前,我们需要优先更新RACCommand里面的executing和enabled信号,所以这里要先把connection.signal加入到self.activeExecutionSignals数组里面。
We subscribe to the signal on the main thread so that it occurs _after_
-addActiveExecutionSignal: completes below.
This means that `executing` and `enabled` will send updated values before
the signal actually starts performing work.
enabled和excuting信号
/// A signal of whether this command is able to execute.
///
/// This will send NO if:
///
/// - The command was created with an `enabledSignal`, and NO is sent upon that
/// signal, or
/// - `allowsConcurrentExecution` is NO and the command has started executing.
///
/// Once the above conditions are no longer met, the signal will send YES.
///
/// This signal will send its current value upon subscription, and then all
/// future values on the main thread.
@property (nonatomic, strong, readonly) RACSignal *enabled;
/// A signal of whether this command is currently executing.
///
/// This will send YES whenever -execute: is invoked and the created signal has
/// not yet terminated. Once all executions have terminated, `executing` will
/// send NO.
///
/// This signal will send its current value upon subscription, and then all
/// future values on the main thread.
@property (nonatomic, strong, readonly) RACSignal *executing;
enabled和executing信号会在订阅时发送信号,之后的信号都会在主线程发出.在处理这几个信号的时候,都用了一次concat操作作者的目的是为了让第一个值以后的每个值都发送在主线程上,所以这里skip:1之后接着deliverOn:RACScheduler.mainThreadScheduler。那第一个值呢?第一个值在一订阅的时候就发送出去了,同订阅者所在线程一致。
_enabled = [[[[[self.immediateEnabled
take:1]
concat:[[self.immediateEnabled skip:1] deliverOn:RACScheduler.mainThreadScheduler]]
distinctUntilChanged]
replayLast]
setNameWithFormat:@"%@ -enabled", self];
schedulerWithPriority:和 schedulerWithPriority: name:两个方法
+ (RACSignal *)interval:(NSTimeInterval)interval
RACSignal (Operations)
在RACSignal (Operations)
内- (RACSignal *)deliverOnMainThread
内部实现
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
__block volatile int32_t queueLength = 0;
void (^performOnMainThread)(dispatch_block_t) = ^(dispatch_block_t block) {
int32_t queued = OSAtomicIncrement32(&queueLength);
if (NSThread.isMainThread && queued == 1) {
block();
OSAtomicDecrement32(&queueLength);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
block();
OSAtomicDecrement32(&queueLength);
});
}
};
return [self subscribeNext:^(id x) {
performOnMainThread(^{
[subscriber sendNext:x];
});
} error:^(NSError *error) {
performOnMainThread(^{
[subscriber sendError:error];
});
} completed:^{
performOnMainThread(^{
[subscriber sendCompleted];
});
}];
}] setNameWithFormat:@"[%@] -deliverOnMainThread", self.name];
}
RACCommand的error订阅
我们知道,在对RACCommand进行错误处理的时候,我们不应该使用subscribeError:对RACCommand的executionSignals
进行错误的订阅,因为executionSignals这个信号是不会发送error事件的,那当RACCommand包裹的信号发送error事件时,我们要怎样去订阅到它呢?应该用subscribeNext:去订阅错误信号。
// `errors` needs to be multicasted so that it picks up all
// `activeExecutionSignals` that are added.
//
// In other words, if someone subscribes to `errors` _after_ an execution
// has started, it should still receive any error from that execution.
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:操作,回到主线程中去操作。最后把这个冷信号转换成热信号.
executionSignals虽然是一个冷信号,但是它是由内部的addedExecutionSignalsSubject的产生的,这是一个热信号,订阅者订阅它的时候需要在execute:执行之前去订阅,否则这个addedExecutionSignalsSubject热信号对已保存的所有的订阅者发送完信号以后,再订阅就收不到任何信号了。所以需要在热信号发送信号之前订阅,把自己保存到热信号的订阅者数组里。所以executionSignals的订阅要在execute:执行之前。
而execute:返回的信号是RACReplaySubject热信号,它会把订阅者保存起来,即使先发送信号,再订阅,订阅者也可以收到之前发送的值。
两个信号虽然信号内容都相同,但是订阅的先后次序不同,executionSignals必须在execute:执行之前去订阅,而execute:返回的信号是在execute:执行之后去订阅的。
RACCommand的分类
UIButton+RACCommandSupport
- (void)rac_hijackActionAndTargetIfNeeded {
SEL hijackSelector = @selector(rac_commandPerformAction:);
for (NSString *selector in [self actionsForTarget:self forControlEvent:UIControlEventTouchUpInside]) {
if (hijackSelector == NSSelectorFromString(selector)) {
return;
}
}
[self addTarget:self action:hijackSelector forControlEvents:UIControlEventTouchUpInside];
}
- (void)rac_commandPerformAction:(id)sender {
[self.rac_command execute:sender];
}
如果同时设置了button.rac_command和[button addTarget: selector:],相当于调用了多次[button addTarget: selector:],看一下苹果官文说明
You may call this method multiple times to configure multiple targets and actions for the control. It is also safe to call this method multiple times with the same values for the target and action parameters. The control maintains a list of its attached targets and actions along and the events each supports.
The control does not retain the object in the target parameter. It is your responsibility to maintain a strong reference to the target object while it is attached to a control.
Specifying a value of 0 for the controlEvents parameter does not prevent events from being sent to a previously registered target and action method. To stop the delivery of events, always call the removeTarget(_:action:for:) method.
所以,如果设置了button.rac_command之后又使用了addTarget来添加事件,就会调用两次.