RACCommandSupport
在 ReactiveCocoa 框架中,RACCommand 类可以将信号流作为信号量进行接收,从而可以封装一系列处理不同任务的信号流。为了扩展该类的用法,框架为下面几个类声明了 RACCommandSupport 分类。
- UIBarButtonItem
- UIButton
- UIRefreshControl
这三个类所扩展的分类中都声明了一个属性,并实现了设置和获取方法。
@property (nonatomic, strong, nullable) RACCommand<__kindof UIRefreshControl *, id> *rac_command;
UIBarButtonItem (RACCommandSupport)
和 UIButton (RACCommandSupport)
的实现方式类似,在调用下面的设置方法时,都会将控件的可用性绑定到命令的可用性上。
- (void)setRac_command:(RACCommand *)command {
···
disposable = [command.enabled setKeyPath:@keypath(self.enabled) onObject:self];
···
}
除此之外,该设置方法还会将控件的事件响应方法改变为命令的执行方法。
- (void)rac_commandPerformAction:(id)sender {
[self.rac_command execute:sender];
}
所以,可以将按钮点击任务封装在 RACCommand 命令中,如下例子:
- (void)test
{
RACCommand *command = [[RACCommand alloc]initWithSignalBlock:^RACSignal * _Nonnull(UIButton * _Nullable input) {
input.tag++;
RACSignal *signal1 = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:[NSString stringWithFormat:@"signal1 send a next signal and input is %@",input]];
return nil;
}];
RACSignal *signal2 = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:[NSString stringWithFormat:@"signal2 send a next signal and input is %@",input]];
return nil;
}];
return input.tag%2 == 1 ? signal1 : signal2;
}];
command.allowsConcurrentExecution = YES;
self.testBtn.rac_command = command;
[command.executionSignals.switchToLatest subscribeNext:^(id _Nullable x) {
NSLog(@"%@",x);
}];
}
如此,便可以通过一个值,如这里的 input.tag
,来执行不同的任务。
运行结果:
2018-11-02 15:44:52.776475+0800 testP[4507:556667] signal1 send a next signal and input is <UIButton: 0x7fee1900afe0; frame = (0 0; 100 100); opaque = NO; tag = 1; layer = <CALayer: 0x600000f39100>>
2018-11-02 15:44:53.338504+0800 testP[4507:556667] signal2 send a next signal and input is <UIButton: 0x7fee1900afe0; frame = (0 0; 100 100); opaque = NO; tag = 2; layer = <CALayer: 0x600000f39100>>
UIRefreshControl (RACCommandSupport)
分类中设置该属性的实现方式与上面两个分类有所不同,为便于理解,将代码拆分如下:
- (void)setRac_command:(RACCommand *)command {
objc_setAssociatedObject(self, UIRefreshControlRACCommandKey, command, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[objc_getAssociatedObject(self, UIRefreshControlDisposableKey) dispose];
if (command == nil) return;
RACDisposable *enabledDisposable = [command.enabled setKeyPath:@keypath(self.enabled) onObject:self];
RACSignal *signal1 = [self rac_signalForControlEvents:UIControlEventValueChanged];
RACSignal *signal2 = [signal1 map:^(UIRefreshControl *x) {
RACSignal *signalInner1 = [command execute:x];
RACSignal *signalInner2 = [signalInner1 catchTo:[RACSignal empty]];
RACSignal *signalInner3 = [signalInner2 then:^{
return [RACSignal return:x];
}];
return signalInner3;
}];
RACSignal *signal3 = [signal2 concat];
RACSignal *signal4 = [signal3 deliverOnMainThread];
RACDisposable *executionDisposable = [signal4 subscribeNext:^(UIRefreshControl *x) {
[x endRefreshing];
}];
RACDisposable *commandDisposable = [RACCompoundDisposable compoundDisposableWithDisposables:@[ enabledDisposable, executionDisposable ]];
objc_setAssociatedObject(self, UIRefreshControlDisposableKey, commandDisposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
该方法的实现步骤如下:
-
使用 UIRefreshControlRACCommandKey 将提供的参数 command 绑定到当前控件,完成属性赋值。
-
使用 UIRefreshControlDisposableKey 处理上个命令的相关释放操作。
-
判断 command 参数,如果为 nil ,则直接返回,否则继续执行。
-
将控件的 enabled 属性与 command 的 enabled 属性相绑定,即如控件不可用,那么命令不可执行。
-
为控件绑定 UIControlEventValueChanged 事件,得到 signal1 信号流,当事件被触发后,该信号流将当前控件作为信号量进行传递。
-
将接收到的控件信号量进行映射,映射为一个信号流类型的信号量,在信号流 signal2 中传递,在映射之前会先执行命令。
1)当 signal1 中传递一个信号量时,命令 command 将其当作参数执行命令方法, 返回 signalInner1 信号流。 2)signalInner2 信号流传递的信号量同信号流 signalInner1 一样, 只是并不会传递错误信号量。 3)当 signalInner2 信号流结束后,即 signalInner1 发送了结束信号量, signalInner3 信号流便会传递最初 signal1 传递的控件信号量。 4)返回最后的映射值 signalInner3 信号流。
-
接着,signal2 会拼接自身传递的信号量(即信号流 signalInner3),从而得到新的信号流 signal3 。
-
保证 signal3 的信号量在主线程进行传递,得到 signal4 。
-
订阅 signal4 信号量,接收到控件信号量时,便停止该控件的刷新。
-
设置释放操作。