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是否可用,第二个是执行的block。enableed默认是yes,所以第二个参数也可以为nil。
8.execute:(id)input:调用command,input为executionSignals的订阅者发送的值
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组件使用
-