@myron-lee
2017-05-23T05:55:39.000000Z
字数 3787
阅读 2236
RAC
IOS
菜鸟
RACCommand 经常与 UIButton 结合使用,防止 UIButton 被多次点击,造成类似订单重复提交问题。但是下面这种用法不能阻止订单重复提交。
self.submitBtn.rac_command = [[RACCommand alloc]
initWithEnabled:RACObserve(self.viewModel, agreeProtocol)
signalBlock:^RACSignal *(id input) {
[[self.viewModel submitOrder] subscribeNext:^(id *value) {
//Do something
} error:^(NSError *error) {
//Do something
}];
return [RACSignal empty];
}];
RACCommand 的内部实现原理是,用户点击按钮的时候,如果 RACCommand中没有正在执行的 Signal, RACCommand 会执行signalBlock生成一个 Signal加入到RACComand 中进行管理。上面的用法中,signalBlock返回了一个空的 Signal,它会立即 sendComplete,如果用户点击, RACCommand 将会添加新的 Signal。在这个场景下,结果就是订单被重复提交。
正确用法
self.submitBtn.rac_command =
[[RACCommand alloc] initWithEnabled:RACObserve(self.viewModel, agreeProtocol)
signalBlock:^RACSignal *(id input) {
return [self.viewModel submitOrder];
}];
[[self.submitBtn.rac_command.executionSignals flatten] subscribeNext:^(id value) {
//Do something
}];
[self.submitBtn.rac_command.errors subscribeNext:^(NSError *error) {
//Do something
}];
从上面看到 RACCommand 把 Error 都转发到 errors 这个 Signal 中了。(这么做的目的可能是防止一个 signal 的 error 影响 signal of signal 的运行)但是这样有个缺点,就是逻辑比较分散。下面这个方法可以解决。
self.submitBtn.rac_command =
[[RACCommand alloc] initWithEnabled:RACObserve(self.viewModel, agreeProtocol)
signalBlock:^RACSignal *(id input) {
@strongify(self);
return [[self.viewModel submitOrder] materialize];
}];
[[self.submitBtn.rac_command.executionSignals flatten] subscribeNext:^(RACEvent *event) {
@strongify(self);
if ([event eventType] == RACEventTypeNext) {
id value = [event value];
//Do something
} else if ([event eventType] == RACEventTypeError) {
NSError *error = [event value];
//Do something
}
}] ;
这里用到了 materialize Operation。它的实现其实比较简单,即把 Next、Error、Complete 都包装成RACEvent,通过 sendNext 传送。
- (RACSignal *)materialize {
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
return [self subscribeNext:^(id x) {
[subscriber sendNext:[RACEvent eventWithValue:x]];
} error:^(NSError *error) {
[subscriber sendNext:[RACEvent eventWithError:error]];
[subscriber sendCompleted];
} completed:^{
[subscriber sendNext:RACEvent.completedEvent];
[subscriber sendCompleted];
}];
}] setNameWithFormat:@"[%@] -materialize", self.name];
}
组合输入参数,去重,使用参数创建网络请求Signal,监听最新的网络请求Signal(有新的网络请求时,旧的网络请求会取消),获取结果并处理。
[[[[[RACSignal
combineLatest:@[paramSignalA, paramSignalB]
reduce:^id(id paramA, id paramB){
}]
distinctUntilChanged]
map:^id(id params) {
return [[[XXNetService queryXX:params];
}]
switchToLatest]
subscribeNext:^(NSNumber *x) {
//Do something
}];
switchToLatest的实现如下,其中关键语句是[x takeUntil:[connection.signal concat:[RACSignal never]]]
,这条语句的效果是网络请求signal在signal of signals(可以理解为网络请求的队列)发送了一个消息(一个新的网络请求signal)之后会dispose掉自己,后来者居上,永远都是最新的网络请求signal在发送消息。这正是我们想要的。
- (RACSignal *)switchToLatest {
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
RACMulticastConnection *connection = [self publish];
RACDisposable *subscriptionDisposable = [[connection.signal
flattenMap:^(RACSignal *x) {
NSCAssert(x == nil || [x isKindOfClass:RACSignal.class], @"-switchToLatest requires that the source signal (%@) send signals. Instead we got: %@", self, x);
// -concat:[RACSignal never] prevents completion of the receiver from
// prematurely terminating the inner signal.
return [x takeUntil:[connection.signal concat:[RACSignal never]]];
}]
subscribe:subscriber];
RACDisposable *connectionDisposable = [connection connect];
return [RACDisposable disposableWithBlock:^{
[subscriptionDisposable dispose];
[connectionDisposable dispose];
}];
}] setNameWithFormat:@"[%@] -switchToLatest", self.name];
}
上述代码存在一个问题,一但一次网络请求出错,整个流程将被终止(sendError之后紧接着就会sendCompleted)。使用catch这个Operation可以解决这个问题,catch住Error Event,处理一下,把这个出错了的网络请求Signal替换为一个空的Signal, 不让这个Error Event影响整个流程。加入Catch之后的流程如下:
[[[[[RACSignal
combineLatest:@[paramSignalA, paramSignalB]
reduce:^id(id paramA, id paramB){
}]
distinctUntilChanged]
map:^id(id params) {
return [[XXNetService queryXX:params] catch:^RACSignal *(NSError *error) {
return [RACSignal empty];
}];;
}]
switchToLatest]
subscribeNext:^(NSNumber *x) {
//Do something
}];