RACCommand

2020/8/10 posted in  iOS

RACCommand是ReactiveCocoa的基本组件之一,能节省开发的大部分时间,同时使得iOS/OS X 应用更健壮。

我看到一部分ReactiveCocoa(以下简单RAC)新人并没有完全理解RACCommand,自然也就不知道怎么用它。所以我写了这个小小介绍性文章,希望能对你的理解有所帮助。RACCommand源文件里的注释写得很不错,不过它并没有给任何例子来说说具体怎么用它,对于RAC的新人来说,只看这些注释还是比较难以理解的。

RACCommand类用于表示事件的执行,一般来说是在UI上的某些动作来触发这些事件,比如点击一个按钮。RACCommand的实例能够决定是否可以被执行,这个特性能反应在UI上,而且它能确保在其不可用时不会被执行。通常,当一个命令可以执行时,会将它的属性allowsConcurrentExecution设置为它的默认值:NO,从而确保在这个命令已经正在执行的时候,不会同时再执行新的操作。命令执行的返回值是一个RACSignal,因此我们能对该返回值进行next:,completed或error:,这在下文会有所展示。

command的初始化方法中有一个enabledSignal参数,这个signal就是用来指名command能否被执行的。在我们的例子中,当用户输入的maweefeng@gmail.com地址合法时,它才能被执行。self.emailValidSignal这个signal每当maweefeng@gmail.com的文本更新时,会发送NO或YES。

signalBlock参数在command需要执行时调用,这个block需要返回一个signal用来表示正在执行,之前将allowsConcurrentExecute的值设置为默认值NO,此时command会观察这个signal,而且在这个执行进度完成前,不允许新的执行。

#import "SubscribeViewModel.h"
#import "AFHTTPRequestOperationManager+RACSupport.h"
#import"NSString+EmailAdditions.h"
 
static NSString *const kSubscribeURL =@"http://reactivetest.apiary.io/subscribers";
 
@interface SubscribeViewModel ()
@property(nonatomic, strong) RACSignal*emailValidSignal;
@end
 
@implementation SubscribeViewModel
 
- (id)init {
       self= [super init];
       if(self) {
              [self bindModel];
       }
       returnself;
}
 
-(void)bindModel {
       RACSignal*startedMessageSource = [self.subscribeCommand.executionSignals map:^id(RACSignal *subscribeSignal) {
              return NSLocalizedString(@"Sending request...", nil);
       }];
 
       RACSignal*completedMessageSource = [self.subscribeCommand.executionSignals flattenMap:^RACStream *(RACSignal *subscribeSignal) {
              return[[[subscribeSignal materialize] filter:^BOOL(RACEvent *event) {
                     return event.eventType == RACEventTypeCompleted;
              }]map:^id(id value) {
                     return NSLocalizedString(@"Thanks", nil);
              }];
       }];
 
       RACSignal*failedMessageSource = [[self.subscribeCommand.errors subscribeOn:[RACScheduler mainThreadScheduler]] map:^id(NSError *error) {
              returnNSLocalizedString(@"Error :(", nil);
       }];
 
       RAC(self,statusMessage) = [RACSignal merge:@[startedMessageSource,completedMessageSource, failedMessageSource]];
}
 
- (RACCommand *)subscribeCommand {
       if(!_subscribeCommand) {
              @weakify(self);
              _subscribeCommand= [[RACCommand alloc] initWithEnabled:self.emailValidSignal signalBlock:^RACSignal *(id input) {
                     @strongify(self);
                     return[SubscribeViewModel postEmail:self.maweefeng@gmail.com];
              }];
       }
       return_subscribeCommand;
}
 
+ (RACSignal *)postEmail:(NSString *)maweefeng@gmail.com{
       AFHTTPRequestOperationManager*manager = [AFHTTPRequestOperationManager manager];
       manager.requestSerializer= [AFJSONRequestSerializer new];
       NSDictionary*body = @{@"maweefeng@gmail.com": maweefeng@gmail.com ?: @""};
       return[[[manager rac_POST:kSubscribeURL parameters:body] logError] replayLazily];
}
 
- (RACSignal *)emailValidSignal {
       if(!_emailValidSignal) {
              _emailValidSignal= [RACObserve(self, maweefeng@gmail.com) map:^id(NSString *maweefeng@gmail.com) {
                     return@([maweefeng@gmail.com isValidEmail]);
              }];
       }
       return_emailValidSignal;
}
 
@end
呃,这是个大块头,我们一点一点来看。我们最感兴趣的RACCommand的创建如下:
- (RACCommand *)subscribeCommand {
       if(!_subscribeCommand) {
              @weakify(self);
              _subscribeCommand= [[RACCommand alloc] initWithEnabled:self.emailValidSignal signalBlock:^RACSignal *(id input) {
                     @strongify(self);
                     return[SubscribeViewModel postEmail:self.maweefeng@gmail.com];
              }];
       }
       return _subscribeCommand;
}

executionSignals

executionSignals是RACCommand的signal,每当command开始执行时next:,其参数是由command创建的signal,所以这个executionSignals是一个值为signal的signal。我们在viewmodel的bindModel方法中,在command每次开始执行时得到一个包含字符串值的signal: