[关闭]
@myron-lee 2017-05-23T05:55:39.000000Z 字数 3787 阅读 2236

RAC实践

RAC IOS 菜鸟


1. RACCommand

RACCommand 经常与 UIButton 结合使用,防止 UIButton 被多次点击,造成类似订单重复提交问题。但是下面这种用法不能阻止订单重复提交。

  1. self.submitBtn.rac_command = [[RACCommand alloc]
  2. initWithEnabled:RACObserve(self.viewModel, agreeProtocol)
  3. signalBlock:^RACSignal *(id input) {
  4. [[self.viewModel submitOrder] subscribeNext:^(id *value) {
  5. //Do something
  6. } error:^(NSError *error) {
  7. //Do something
  8. }];
  9. return [RACSignal empty];
  10. }];

RACCommand 的内部实现原理是,用户点击按钮的时候,如果 RACCommand中没有正在执行的 Signal, RACCommand 会执行signalBlock生成一个 Signal加入到RACComand 中进行管理。上面的用法中,signalBlock返回了一个空的 Signal,它会立即 sendComplete,如果用户点击, RACCommand 将会添加新的 Signal。在这个场景下,结果就是订单被重复提交。
正确用法

  1. self.submitBtn.rac_command =
  2. [[RACCommand alloc] initWithEnabled:RACObserve(self.viewModel, agreeProtocol)
  3. signalBlock:^RACSignal *(id input) {
  4. return [self.viewModel submitOrder];
  5. }];
  6. [[self.submitBtn.rac_command.executionSignals flatten] subscribeNext:^(id value) {
  7. //Do something
  8. }];
  9. [self.submitBtn.rac_command.errors subscribeNext:^(NSError *error) {
  10. //Do something
  11. }];

2.materialize

从上面看到 RACCommand 把 Error 都转发到 errors 这个 Signal 中了。(这么做的目的可能是防止一个 signal 的 error 影响 signal of signal 的运行)但是这样有个缺点,就是逻辑比较分散。下面这个方法可以解决。

  1. self.submitBtn.rac_command =
  2. [[RACCommand alloc] initWithEnabled:RACObserve(self.viewModel, agreeProtocol)
  3. signalBlock:^RACSignal *(id input) {
  4. @strongify(self);
  5. return [[self.viewModel submitOrder] materialize];
  6. }];
  7. [[self.submitBtn.rac_command.executionSignals flatten] subscribeNext:^(RACEvent *event) {
  8. @strongify(self);
  9. if ([event eventType] == RACEventTypeNext) {
  10. id value = [event value];
  11. //Do something
  12. } else if ([event eventType] == RACEventTypeError) {
  13. NSError *error = [event value];
  14. //Do something
  15. }
  16. }] ;

这里用到了 materialize Operation。它的实现其实比较简单,即把 Next、Error、Complete 都包装成RACEvent,通过 sendNext 传送。

  1. - (RACSignal *)materialize {
  2. return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
  3. return [self subscribeNext:^(id x) {
  4. [subscriber sendNext:[RACEvent eventWithValue:x]];
  5. } error:^(NSError *error) {
  6. [subscriber sendNext:[RACEvent eventWithError:error]];
  7. [subscriber sendCompleted];
  8. } completed:^{
  9. [subscriber sendNext:RACEvent.completedEvent];
  10. [subscriber sendCompleted];
  11. }];
  12. }] setNameWithFormat:@"[%@] -materialize", self.name];
  13. }

3.实时网络请求(搜索、计算优惠过后的实际支付金额)

3.1 基本流程

组合输入参数,去重,使用参数创建网络请求Signal,监听最新的网络请求Signal(有新的网络请求时,旧的网络请求会取消),获取结果并处理。

  1. [[[[[RACSignal
  2. combineLatest:@[paramSignalA, paramSignalB]
  3. reduce:^id(id paramA, id paramB){
  4. }]
  5. distinctUntilChanged]
  6. map:^id(id params) {
  7. return [[[XXNetService queryXX:params];
  8. }]
  9. switchToLatest]
  10. subscribeNext:^(NSNumber *x) {
  11. //Do something
  12. }];

3.2 switchToLatest

switchToLatest的实现如下,其中关键语句是[x takeUntil:[connection.signal concat:[RACSignal never]]],这条语句的效果是网络请求signal在signal of signals(可以理解为网络请求的队列)发送了一个消息(一个新的网络请求signal)之后会dispose掉自己,后来者居上,永远都是最新的网络请求signal在发送消息。这正是我们想要的。

  1. - (RACSignal *)switchToLatest {
  2. return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
  3. RACMulticastConnection *connection = [self publish];
  4. RACDisposable *subscriptionDisposable = [[connection.signal
  5. flattenMap:^(RACSignal *x) {
  6. NSCAssert(x == nil || [x isKindOfClass:RACSignal.class], @"-switchToLatest requires that the source signal (%@) send signals. Instead we got: %@", self, x);
  7. // -concat:[RACSignal never] prevents completion of the receiver from
  8. // prematurely terminating the inner signal.
  9. return [x takeUntil:[connection.signal concat:[RACSignal never]]];
  10. }]
  11. subscribe:subscriber];
  12. RACDisposable *connectionDisposable = [connection connect];
  13. return [RACDisposable disposableWithBlock:^{
  14. [subscriptionDisposable dispose];
  15. [connectionDisposable dispose];
  16. }];
  17. }] setNameWithFormat:@"[%@] -switchToLatest", self.name];
  18. }

3.3 catch

上述代码存在一个问题,一但一次网络请求出错,整个流程将被终止(sendError之后紧接着就会sendCompleted)。使用catch这个Operation可以解决这个问题,catch住Error Event,处理一下,把这个出错了的网络请求Signal替换为一个空的Signal, 不让这个Error Event影响整个流程。加入Catch之后的流程如下:

  1. [[[[[RACSignal
  2. combineLatest:@[paramSignalA, paramSignalB]
  3. reduce:^id(id paramA, id paramB){
  4. }]
  5. distinctUntilChanged]
  6. map:^id(id params) {
  7. return [[XXNetService queryXX:params] catch:^RACSignal *(NSError *error) {
  8. return [RACSignal empty];
  9. }];;
  10. }]
  11. switchToLatest]
  12. subscribeNext:^(NSNumber *x) {
  13. //Do something
  14. }];
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注