RACCommand使用总结

1、RACCommand是什么

平常使用button可以使用rac_signalForControlEvents获得点击事件的信号,执行点击事件。rac提供了rac_command来执行某个block。首先看下RACCommand的基本属性有哪些:

1. executionSignals:需要执行的block成功的时候返回的信号,他是在主线程执行的。
2. executing:判断当前的block是否在执行,执行完之后会返回@(NO).
3. enabled:当前命令是否enabled,默认是no,他也可以根据enableSignal来设置或者allowsConcurrentExecution设置为NO的时候(command已经开始执行)
4. errors:执行command的时候获取的error都会通过这个信号发送
5. allowsConcurrentExecution:是否允许并发执行command,默认是NO

6.initWithSignalBlock:(RACSignal * (^)(id input))signalBlock:初始化RACCommand,参数为返回一个信号的block,即block返回的是executionSignals
7.- (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(id input))signalBlock:第一个参数设置当前command是否可用,第二个是执行的blockenableed默认是yes,所以第二个参数也可以为nil
8.execute:(id)input:调用commandinputexecutionSignals的订阅者发送的值
2.源码分析

通过以下代码可以看出_executionSignals是最新的activeExecutionSignals,可能是多个(允许并发执行设为yes的时候)。

RACSignal *newActiveExecutionSignals = [[[[[selfrac_valuesAndChangesForKeyPath:@keypath(self.activeExecutionSignals) options:NSKeyValueObservingOptionNew observer:nil]
        reduceEach:^(id _, NSDictionary *change) {
            NSArray *signals = change[NSKeyValueChangeNewKey];
            if (signals == nil) return [RACSignal empty];

            return [signals.rac_sequence signalWithScheduler:RACScheduler.immediateScheduler];
        }]
        concat]
        publish]
        autoconnect];

    _executionSignals = [[[newActiveExecutionSignals
        map:^(RACSignal *signal) {
            return [signal catchTo:[RACSignal empty]];
        }]
        deliverOn:RACScheduler.mainThreadScheduler]
        setNameWithFormat:@"%@ -executionSignals", self];

executing,判断当前信号是否在执行,从下面可以看出,他实际是当前有多少个活跃信号的监听,当有活跃信号的是否就返回YES,否则返回no,并且默认是no。replayLast确保是最新的值,distinctUntilChanged当值发生改变的时候才会返回新值

    RACSignal *immediateExecuting = [RACObserve(self, activeExecutionSignals) map:^(NSArray *activeSignals) {
        return @(activeSignals.count > 0);
    }];

    _executing = [[[[[immediateExecuting
        deliverOn:RACScheduler.mainThreadScheduler]
        // This is useful before the first value arrives on the main thread.
        startWith:@NO]
        distinctUntilChanged]
        replayLast]
        setNameWithFormat:@"%@ -executing", self];

enabled是由enableSignal和是否允许执行两者结合,只有两者都为真的时候才能执行。enbaleSingnal是传入的值,那moreExecutionsAllowed是个什么呢?,他是实际是[RACSignal
if:RACObserve(self, allowsConcurrentExecution)
then:[RACSignal return:@YES]
else:[immediateExecuting not]]这句代码返回的。如果允许并发,enable会始终是yes,如果不允许则判断当前是否有可以立即执行的信号,有的话enable返回no,如果没有,则command当前是可以执行的,返回yes。


    _immediateEnabled = [[RACSignal
        combineLatest:@[ enabledSignal, moreExecutionsAllowed ]]
        and];

    _enabled = [[[[[self.immediateEnabled
        deliverOn:RACScheduler.mainThreadScheduler]
        startWith:@YES]
        distinctUntilChanged]
        replayLast]
        setNameWithFormat:@"%@ -enabled", self];

errors:当最新执行的信号发送错误信号时会把它赋值给errors,中间经过了connect

    RACMulticastConnection *errorsConnection = [[[newActiveExecutionSignals
        flattenMap:^(RACSignal *signal) {
            return [[signal
                ignoreValues]
                catch:^(NSError *error) {
                    return [RACSignal return:error];
                }];
        }]
        deliverOn:RACScheduler.mainThreadScheduler]
        publish];

    _errors = [errorsConnection.signal setNameWithFormat:@"%@ -errors", self];

allowsConcurrentExecution:允许并发执行后,enable会始终返回yes,会在execute的时候用到,如果返回no,则不会执行block

    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];
    }

execute:

1.判断是否可以执行
    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];
    }
2.获得block返回的信号
    RACSignal *signal = self.signalBlock(input);
    NSCAssert(signal != nil, @"nil signal returned from signal block for value: %@", input);

3.对signal进行连接
    // 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);

4.进行增加和移除操作,增加的是RACReplaySubject
    [self addActiveExecutionSignal:connection.signal];
    [connection.signal subscribeError:^(NSError *error) {
        @strongify(self);
        [self removeActiveExecutionSignal:connection.signal];
    } completed:^{
        @strongify(self);
        [self removeActiveExecutionSignal:connection.signal];
    }];

5.确保sideEffect只执行一次,并且缓存数据
    [connection connect];
    return [connection.signal setNameWithFormat:@"%@ -execute: %@", self, [input rac_description]];
3.如何使用

简单的一个登录操作

1.创建ViewModel

@interface ViewModel : NSObject

@property (nonatomic,copy) NSString *username;
@property (nonatomic,copy) NSString *password;
@property (nonatomic,readonly) RACCommand *loginCommand;

@end

@interface ViewModel ()

@property (nonatomic,strong) RACCommand *loginCommand;

@end

@implementation ViewModel

- (instancetype)init {

    self = [super init];

    if (!self) {
        return nil;
    }

    @weakify(self)
    self.loginCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {

        @strongify(self)
       return [self loginWithUsername:self.username password:self.password];


    }];


    return self;

}

//有效性判断
- (RACSignal *)validateLoginInputs {

    return [RACSignal combineLatest:@[RACObserve(self, username), RACObserve(self, password)] reduce:^id(NSString *username,NSString *password){
        return @(username.length > 0 && password.length > 0);
    }];

}

//真正登录的执行代码
- (RACSignal *)loginWithUsername:(NSString *)username password:(NSString *)password {

    return [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        NSLog(@"beigin login");
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [subscriber sendNext:@"login"];
            [subscriber sendCompleted];
            NSLog(@"end login");
        });
        return nil;
    }] publish] autoconnect];
}

2、绑定viewModle

#import "ViewController.h"
#import <Masonry/Masonry.h>
#import <ReactiveCocoa/ReactiveCocoa.h>
#import <libextobjc/EXTScope.h>
#import "ViewModel.h"

@interface ViewController ()

@property (nonatomic,strong) UITextField *usernameTextField;
@property (nonatomic,strong) UITextField *passwordTextField;
@property (nonatomic,strong) UIButton *button;
@property (nonatomic,strong) ViewModel *viewModel;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    1.view配置
    self.viewModel = [[ViewModel alloc] init];

    self.usernameTextField = [[UITextField alloc] init];
    self.usernameTextField.placeholder = @"输入用户名";
    self.usernameTextField.backgroundColor = [UIColor purpleColor];

    self.passwordTextField = [[UITextField alloc] init];
    self.passwordTextField.placeholder = @"输入密码";
    self.passwordTextField.backgroundColor = [UIColor purpleColor];

    UIView *superView = self.view;
    [self.view addSubview:self.usernameTextField];
    [self.view addSubview:self.passwordTextField];

    @weakify(self)
    [self.usernameTextField mas_makeConstraints:^(MASConstraintMaker *make) {
        make.centerX.equalTo(superView.mas_centerX);
        make.centerY.equalTo(@(10));
        make.width.equalTo(@(200));
        make.height.equalTo(@(35));
    }];
    [self.passwordTextField mas_makeConstraints:^(MASConstraintMaker *make) {

        make.width.mas_equalTo(self.usernameTextField.mas_width);
        make.height.mas_equalTo(self.usernameTextField.mas_height);
        make.centerY.mas_equalTo(self.usernameTextField.mas_centerY).offset(45);
        make.centerX.mas_equalTo(self.usernameTextField.mas_centerX);

    }];
        self.button = [UIButton buttonWithType:UIButtonTypeCustom];
    [self.view addSubview:self.button];
    [self.button mas_makeConstraints:^(MASConstraintMaker *make) {
        make.centerX.equalTo(self.view.mas_centerX);
        make.centerY.equalTo(self.view.mas_bottom).offset(-40);
        make.width.mas_equalTo(200);
        make.height.mas_equalTo(30);
    }];
    [self.button setTitle:@"登陆" forState:UIControlStateNormal];
    self.button.backgroundColor = [UIColor redColor];

    2、绑定viewModel
    [self.usernameTextField.rac_textSignal subscribeNext:^(id x) {
        @strongify(self)
        self.viewModel.username = x;
    }];
    [[self.passwordTextField rac_textSignal] subscribeNext:^(id x) {
        @strongify(self)
        self.viewModel.password = x;
    }];

    self.button.rac_command = self.viewModel.loginCommand;

    //判断是否正在执行
    [self.button.rac_command.executing subscribeNext:^(id x) {
        if ([x boolValue]) {
            NSLog(@"login..");
        } else {
            NSLog(@"end logining");
        }
    }];

    //执行结果
    [self.button.rac_command.executionSignals.flatten subscribeNext:^(id x) {
        NSLog(@"result:%@",x);
    }];

    //错误处理
    [self.button.rac_command.errors subscribeNext:^(id x) {
        NSLog(@"error:%@",x);
    }];

}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [self.view endEditing:YES];
}

@end

3、以上是非常简单的viewModel和command使用例子。下面是其他的较为复杂的例子:command组件使用
-

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值