ReactiveCocoa 学习 - 8

RACCommand的使用与学习

2016-06-16 | 阅读

RACCommand

RACCommand对信号进行封装,是信号在某些事件发生时触发,一般与UI操作结合.RACCommand是对信号的一层非常漂亮的封装,用事件来触发信号执行的设计,使RACCommand适用于许多情况,不仅仅是UI操作,其内部对信号热化,以及监控信号状态,可以用于一些耗时操作的信号化,如网络请求.

我们首先来看一下,RACCommand提供的接口:

executionSignals

在调用execute:后,一个返回信号的信号. 这个信号是一个信号的信号,封装了workSignal(本文之后用workSignal这个词来形容用户于RACCommand中要执行的信号).当receiverenable的时候,发送信号.RACCommand将正在执行的信号封装在这里,作为executionSignals的返回值,而信号中的error被发往RACCommanderrors的信号中了,而遇到error信号时,executionSignals的信号会返回一个completed信号以标记事件完成:

RACCommand *comd = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:[NSString stringWithFormat:@"INPUT :%@",input]];
        [subscriber sendError:[NSError errorWithDomain:@"ddd" code:1 userInfo:nil]];
        return nil;
    }];
}];

[[comd.executionSignals switchToLatest]
 subscribeNext:^(id x) {
     NSLog(@"Next : %@",x);
 } error:^(NSError *error) {
     NSLog(@"Next : %@",error);
 } completed:^{
     NSLog(@"Completed");
 }];

[comd.executionSignals subscribeNext:^(id x) {
    NSLog(@"Signal %@ ",x);
}];

[comd.errors subscribeNext:^(id x) {
    NSLog(@"error 里才有? %@",x);
}];

[comd execute:@"hello world"];

如上代码,最终输出为:

2016-07-02 20:39:13.068 TestPods[23173:3420416] Signal <RACDynamicSignal: 0x7ff2d9406d30> name:  
2016-07-02 20:39:13.068 TestPods[23173:3420416] Next : INPUT :hello world
2016-07-02 20:39:13.069 TestPods[23173:3420416] Completed
2016-07-02 20:39:13.069 TestPods[23173:3420416] error 里才有? Error Domain=ddd Code=1 "(null)"

但是,可以通过materialize方法来获取这个inner errors.

RACCommandexecutionSignals封装workSignal,workSignal的执行必须调用excute来驱动.内部信号的订阅操作会在主线程中执行.

executing

表示当前命令是否正在执行的信号.RACCommandexcute调用后,且在信号终止前,这个信号会发送YES.当信号结束了,会发送NO.

RACCommand中的信号全是热信号,可以随便订阅,且所有订阅结果都在主线程中执行. 这个executing使用的是replayLast,所以订阅时就会获得当前执行的状态.

上面说到,当workSignal失败时,也会发送一个Completed的事件,而这个事件也是executing正确处理信号状态的前提.所以在workSignal一定要正确处理信号的状态,在信号处理完成或者失败的时候,要正确地发送Completed或者error事件.

enabled

决定 workSignal是否可以执行的 信号.

只在两种情况下返回 NO :

  • 这个RACCommand是使用initWithEnabled:signalBlock:初始化,即设置了一个enabledSignal,而且这个信号当前返回NO.
  • allowsConcurrentExecution属性设置为 NO,且这个信号正在执行中.

除了这两种情况,一般都返回YES.

这个信号 一般用于操作UI控件的状态,如UIButton的状态,当未满足某些判断逻辑时,enabled为NO,同时设置UIButton不可点击.

errors

workSignal的错误事件被转发到这里.

注意,这里错误订阅需要订阅Next事件,而不是Error事件,因为在RACSteam中,错误事件的发生会关闭信号流.

allowsConcurrentExecution

workSignal信号是否支持并发.

默认是NO,即RACCommand封装的workSignal同时只能有一个信号正在执行.

- (id)initWithSignalBlock:(RACSignal * (^)(id input))signalBlock;

以一个返回workSignalsignalBlock初始化,没有enableBlock.

- (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(id input))signalBlock;

初始化时,设置 signalBlock,和enabledSignal.

对于enabledSignal,初始状态是YES, 即可用.

对于signalBlock,其中返回一个workSignal,这个信号可以带一个输入值,返回的workSignal会以 executionSignals的值的形式被订阅者获取,workSignal会被加热,而executionSignals本身也是一个热信号.

- (RACSignal *)execute:(id)input;

RACCommand是enable的时候,调用execute,

  1. 执行signalBlock中的初始化操作,以输入值input来初始化一个新的workSignal
  2. 加热workSignal,使用RACReplaySubject.
  3. 将这个热的workSignalexecutionSignals上发送.
  4. 订阅者通过switchToLatest来获取这个workSignal,并在主线程中订阅事件.

这个函数是有返回值,返回值也是一个RACSignal,返回值信号为 加热后的workSignal.而如果command的enbaled信号为No时,会返回一个 发送一个RACCommandErrorNotEnabled错误的信号.

总结 RACCommand

RACCommand中,封装的所有信号都是 热信号,订阅事件会发生在主线程上.

我们将workSignal考虑为一个任务,而RACCommand为这个任务提供了非常方便的 状态监控,并发控制,参数传递等功能. 我们可以将这样的一个任务以信号的形式融入RAC的世界中,而不用自己去考虑冷热信号,不用去考虑状态控制.RACCommand是我们使用RAC的一个强力而方便的工具.

我们可以将很多任务通过RACCommand进行封装,如网络请求,类似下面这种封装 :

RACCommand *logginCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id params) {
    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:BASEURL]
                                                                 sessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
        [manager POST:LOGINURL parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
            [subscriber sendNext:responseObject];
            [subscriber sendCompleted];
        } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
            [subscriber sendError:error];
        }];
        return nil;
    }];
}];

像这样通过RACCommand封装网络请求,通过excute:传入一次网络请求的参数,伴随着RACCommand完整的热信号保证,和状态控制,使用ReactiveCocoa会变得更加方便高效.