@myron-lee
2017-05-23T05:55:39.000000Z
字数 3787
阅读 2339
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(有新的网络请求时,旧的网络请求会取消),获取结果并处理。
[[[[[RACSignalcombineLatest:@[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.signalflattenMap:^(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之后的流程如下:
[[[[[RACSignalcombineLatest:@[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}];