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 传递过来一个信号量 signalsignal_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 是否允许同时执行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值