ReactiveCocoa 學習筆記七(RACCommand)
RACCommand
RACCommand 關鍵的兩個方法如下,理解了他們便能理解 RACCommand 的作用。
- (instancetype)initWithEnabled:(nullable RACSignal<NSNumber *> *)enabledSignal signalBlock:(RACSignal<ValueType> * (^)(InputType _Nullable input))signalBlock;
- (RACSignal<ValueType> *)execute:(nullable InputType)input;
initWithEnabled:signalBlock:
在該方法中,首先對屬性進行了初始化。
_addedExecutionSignalsSubject = [RACSubject new];
_allowsConcurrentExecutionSubject = [RACSubject new];
_signalBlock = [signalBlock copy];
屬性 allowsConcurrentExecutionSubject 是個訊號流,每當屬性 allowsConcurrentExecution 發生變化時,便傳送變化後的值,該值表示 RACCommand 能否同時執行。
屬性 addedExecutionSignalsSubject 同樣是一個訊號流,每當有訊號流作為一個訊號量產生時,便由該訊號流進行傳遞。
屬性 signalBlock 是一個程式碼塊,該程式碼塊執行後返回一個訊號流,是與上面的 addedExecutionSignalsSubject 屬性所要傳遞的訊號流是相關聯的。
接下來,繼續初始化屬性 executionSignals ,這個屬性很重要,其是相關操作所要使用的訊號流。
_executionSignals = [[[self.addedExecutionSignalsSubject
map:^(RACSignal *signal) {
return [signal catchTo:[RACSignal empty]] ;
}]
deliverOn:RACScheduler.mainThreadScheduler]
setNameWithFormat:@"%@ -executionSignals", self];
這裡每當 self.addedExecutionSignalsSubject 傳遞過來一個訊號量(實際是 RACReplaySubject 重播訊號量),均會進行對映,目的是忽略訊號量所傳遞的錯誤訊號量,也就是說,executionSignals 訊號流中傳遞的訊號量都是隻會接收到普通的訊號量和結束訊號量的訊號流。
那麼錯誤訊號量都去哪了呢,難道不進行處理麼,接著往下看。
RACMulticastConnection *errorsConnection = [[[self.addedExecutionSignalsSubject
flattenMap:^(RACSignal *signal) {
return [[signal
ignoreValues]
catch:^(NSError *error) {
return [RACSignal return:error];
}];
}]
deliverOn:RACScheduler.mainThreadScheduler]
publish];
_errors = [errorsConnection.signal setNameWithFormat:@"%@ -errors", self];
[errorsConnection connect];
這段程式碼實際是對傳遞錯誤訊號量的訊號流的初始化。
@property (nonatomic, strong, readonly) RACSignal<NSError *> *errors;
每當 self.addedExecutionSignalsSubject 傳遞過來一個訊號量都會被對映為一個忽略普通訊號量,並且提供一個 RACReturnSignal 例項轉發錯誤訊號量的訊號流。而後,publish 方法的呼叫將產生一個 RACSubject 例項與之相關聯,而這個例項賦值給了 errors 屬性。那麼,errors 屬性所接收到的訊號量都是 NSError 例項,並且這些訊號量都是作為普通訊號量進行傳送的,不會導致 errors 訊號流的關閉。
接下來繼續,就是對 executing 的初始化。
RACSignal *immediateExecuting = [[[[self.addedExecutionSignalsSubject
flattenMap:^(RACSignal *signal) {
return [[[signal
catchTo:[RACSignal empty]]
then:^{
return [RACSignal return:@-1];
}]
startWith:@1];
}]
scanWithStart:@0 reduce:^(NSNumber *running, NSNumber *next) {
return @(running.integerValue + next.integerValue);
}]
map:^(NSNumber *count) {
return @(count.integerValue > 0);
}]
startWith:@NO];
_executing = [[[[[immediateExecuting
deliverOn:RACScheduler.mainThreadScheduler]
// This is useful before the first value arrives on the main thread.
startWith:@NO]
distinctUntilChanged]
replayLast]
setNameWithFormat:@"%@ -executing", self];
首先,應明確 immediateExecuting 訊號流是一個傳遞真假值的訊號流,其初始值為 NO ,而後 distinctUntilChanged 的呼叫使得相同值被忽略,再之後 replayLast 的呼叫,使得屬性 executing 成為了一個 RACReplaySubject 型別的重播訊號流,並且其只會重播最後一個值給訂閱者。
那麼 executing 傳遞的值是如何確定的呢,來分析 immediateExecuting 訊號流,為了便於理解,調整程式碼如下:
RACSignal *signal_1 = [self.addedExecutionSignalsSubject flattenMap:^(RACSignal *signal) {
RACSignal *signal_flattenMap_1 = [signal catchTo:[RACSignal empty]];
RACSignal *signal_flattenMap_2 = [signal_flattenMap_1 then:^{
return [RACSignal return:@-1];
}];
RACSignal *signal_flattenMap_3 = [signal_flattenMap_2 startWith:@1];
return signal_flattenMap_3;
}];
RACSignal *signal_2 = [signal_1 scanWithStart:@0 reduce:^(NSNumber *running, NSNumber *next) {
return @(running.integerValue + next.integerValue);
}];
RACSignal *signal_3 = [signal_2 map:^(NSNumber *count) {
return @(count.integerValue > 0);
}];
RACSignal *immediateExecuting = [signal_3 startWith:@NO];
每當 self.addedExecutionSignalsSubject 傳遞過來一個訊號量 signal 時,都會先對映為 signal_flattenMap_1 從而忽略掉錯誤訊號量,然後接著執行 then: 方法,該方法會忽略掉普通的訊號量,並且會在其後拼接一個訊號流,在這裡就是當 signal_flattenMap_1
結束後,signal_flattenMap_2
便會接收到一個 -1 訊號量,而 signal_flattenMap_2
會前置一個值為 1 的訊號量成為 signal_flattenMap_3 。
綜上所述,每當 self.addedExecutionSignalsSubject 傳遞過來一個訊號量 signal ,signal_1
都會傳遞一個訊號量 1 ,而當 signal 結束後,signal_1 都會發送一個 -1 訊號量。
接著,訊號流 signal_2
便是將 signal_1
傳送的訊號量的累加結果傳遞出去。
而,signal_3
便是將 signal_2
的累加結果轉換為真假值進行傳遞,然後前置一個 NO 初始值成為了 immediateExecuting 訊號流。
所以,得出結論,屬性 executing 訊號流所傳遞的真假值,表示著當前是否存在未結束的訊號流可以傳遞訊號量。
接著是對屬性 immediateEnabled 的初始化,如下:
RACSignal *moreExecutionsAllowed = [RACSignal
if:[self.allowsConcurrentExecutionSubject startWith:@NO]
then:[RACSignal return:@YES]
else:[immediateExecuting not]];
if (enabledSignal == nil) {
enabledSignal = [RACSignal return:@YES];
} else {
enabledSignal = [enabledSignal startWith:@YES];
}
_immediateEnabled = [[[[RACSignal
combineLatest:@[ enabledSignal, moreExecutionsAllowed ]]
and]
takeUntil:self.rac_willDeallocSignal]
replayLast];
方法 if:then:else: 返回了 moreExecutionsAllowed 訊號流,其傳遞的訊號量與 self.allowsConcurrentExecutionSubject 傳遞的訊號量相關,如果其傳真,則 moreExecutionsAllowed 傳 YES ,如果其傳假,那麼 moreExecutionsAllowed 傳遞的是 immediateExecuting 的訊號量的相反值。
enabledSignal 可能由方法提供,也可能為預設值,但都會前置一個 YES 值。
而屬性 immediateEnabled 為上面兩個訊號流的組合,其會將兩個訊號流的最新值進行與運算,然後重播該值。
接著,使用 self.immediateEnabled 初始化 enabled 屬性。
_enabled = [[[[[self.immediateEnabled
take:1]
concat:[[self.immediateEnabled skip:1] deliverOn:RACScheduler.mainThreadScheduler]]
distinctUntilChanged]
replayLast]
setNameWithFormat:@"%@ -enabled", self];
所以,RACCommand 是否可用,取決於初始化時提供的 enabledSignal 訊號流和 allowsConcurrentExecution 屬性,若 enabledSignal 傳遞 YES 值,而 allowsConcurrentExecution 為 NO ,那麼該 RACCommand 是否可用,取決於 signalBlock 執行所返回的訊號流是否結束。
execute:
- (RACSignal *)execute:(id)input {
BOOL enabled = [[self.immediateEnabled first] boolValue];
if (!enabled) {
NSError *error = [NSError errorWithDomain:RACCommandErrorDomain code:RACCommandErrorNotEnabled userInfo:@{
NSLocalizedDescriptionKey: NSLocalizedString(@"The command is disabled and cannot be executed", nil),
RACUnderlyingCommandErrorKey: self
}];
return [RACSignal error:error];
}
RACSignal *signal = self.signalBlock(input);
NSCAssert(signal != nil, @"nil signal returned from signal block for value: %@", input);
RACMulticastConnection *connection = [[signal
subscribeOn:RACScheduler.mainThreadScheduler]
multicast:[RACReplaySubject subject]];
[self.addedExecutionSignalsSubject sendNext:connection.signal];
[connection connect];
return [connection.signal setNameWithFormat:@"%@ -execute: %@", self, RACDescription(input)];
}
從該方法可知,初始化時提供的 signalBlock 程式碼塊執行後返回的訊號流會與一個 RACReplaySubject 例項相關聯,該重播訊號流才會被當作訊號量進行傳遞。
小結
通常,會使用下面的方法建立 RACCommand 例項物件。
- (instancetype)initWithSignalBlock:(RACSignal<ValueType> * (^)(InputType _Nullable input))signalBlock;
該方法實際是呼叫了 initWithEnabled:signalBlock: 方法,只是第一個引數傳了 nil 。
開始任務,便執行下面的方法:
- (RACSignal<ValueType> *)execute:(nullable InputType)input;
介面中可用的屬性如下:
@property (nonatomic, strong, readonly) RACSignal<RACSignal<ValueType> *> *executionSignals;
@property (nonatomic, strong, readonly) RACSignal<NSNumber *> *executing;
@property (nonatomic, strong, readonly) RACSignal<NSNumber *> *enabled;
@property (nonatomic, strong, readonly) RACSignal<NSError *> *errors;
@property (atomic, assign) BOOL allowsConcurrentExecution;
executionSignals 屬性作為一個訊號流,其傳遞的訊號量也是訊號流,通常,可以直接呼叫 switchToLatest 方法得到一個訊號流,然後訂閱該訊號流。
executing 屬性傳遞的訊號量表示執行 execute 方法所傳遞的訊號流是否尚未結束,所以訂閱這個訊號流,可以監聽 RACCommand 是否執行結束。
enabled 屬性傳遞的訊號量表示當前 RACCommand 是否可以執行。
errors 屬性傳遞所有發生的錯誤。
allowsConcurrentExecution 屬性表示 RACCommand 是否允許同時執行。