How to combine two asynchronous network calls with ReactiveCocoa

I have two network signals that I want to combine, but with some limitations.

Call the network signals A and B. A uses AFNetworking to search for a resource in the cache and immediately returns any response for this request. B also considers the cache, but can go to a remote server to re-check the response.

Ok, so what I want to do:

Request A:

  • send sendNext as soon as possible.
  • If B has already executed sendNext, we simply ignore A.
  • if something goes wrong and A creates an error, we should just ignore it.

Request B:

  • should send sendNext as soon as possible, even if A has already executed sendNext.
  • If something goes wrong, I got the error from B, but should not stop A.

My current solution is this:

- (RACSignal *)issueById:(NSString *)issueId { RACSignal *filterSignal = [RACSignal createSignal:^RACDisposable *(id <RACSubscriber> subscriber) { RACSignal *cacheSignal = [[IssueWSRequest instance] issueWithId:issueId cachePolicy:NSURLRequestReturnCacheDataDontLoad]; return [cacheSignal subscribeNext:^(id x) { [subscriber sendNext:x]; } error:^(NSError *error) { NSLog(@"Ignore error"); [subscriber sendCompleted]; } completed:^{ [subscriber sendCompleted]; }]; }]; RACSignal *remoteSignal = [[IssueWSRequest instance] issueWithId:issueId cachePolicy:NSURLRequestUseProtocolCachePolicy]; RACSignal *combined = [RACSignal merge:@[newSign, remoteSignal]]; return combined; } 

I know that this solution does not meet my requirements, so I am wondering if anyone can help me with a better solution.

My solution (derived from @ JustinSpahr-Summers answer):

 - (RACSignal *)issueById:(NSString *)issueId { RACSubject *localErrors = [RACSubject subject]; RACSignal *remoteSignal = [[IssueWSRequest instance] issueWithId:issueId cachePolicy:NSURLRequestUseProtocolCachePolicy]; RACSignal *cacheSignal = [[[[[[IssueWSRequest instance] issueWithId:issueId cachePolicy:NSURLRequestReturnCacheDataDontLoad] takeUntil:remoteSignal] doError:^(NSError *error) { [localErrors sendNext:error]; }] finally:^{ // Make sure to complete the subject, since infinite signals are // difficult to use. [localErrors sendCompleted]; }] replayLazily]; return [RACSignal merge:@[ [cacheSignal catchTo:[RACSignal empty]], remoteSignal ]]; } 
+8
objective-c reactive-cocoa
source share
1 answer

This is a tricky question because your desired error handling is fundamentally incompatible with the RACSignal API RACSignal , which states that errors have exception semantics .

The only way to ignore, but still take care of the errors, is to redirect them to another place. In this example, I will use the theme:

 RACSubject *remoteErrors = [RACSubject subject]; 

... but you can also use a property or some other notification mechanism.

I will continue to use the remoteSignal and cacheSignal tags that you specified above, with some changes. Here is the behavior we want from them:

  • remoteSignal should send its errors to remoteErrors
  • cacheSignal should be discarded as soon as remoteSignal sends the value
  • Errors from any signal should not interrupt another
  • We want to combine the values ​​from cacheSignal and remoteSignal so that we still get the remote value after reading the cache.

With that in mind, take a look at remoteSignal :

 RACSignal *remoteSignal = [[[[[IssueWSRequest instance] issueWithId:issueId cachePolicy:NSURLRequestUseProtocolCachePolicy] doError:^(NSError *error) { [remoteErrors sendNext:error]; }] finally:^{ // Make sure to complete the subject, since infinite signals are // difficult to use. [remoteErrors sendCompleted]; }] replayLazily]; 

-doError: and -finally: manage the remoteErrors theme, fulfilling our first requirement above. Since we need to use remoteSignal in more than one place (as you can see in the list above), we use -replayLazily to make sure that its side effects occur only once.

cacheSignal almost unchanged. We just need to use -takeUntil: to ensure that it exits when remoteSignal sends a value (but does not send an error):

 RACSignal *cacheSignal = [[[IssueWSRequest instance] issueWithId:issueId cachePolicy:NSURLRequestReturnCacheDataDontLoad] takeUntil:remoteSignal]; 

Finally, we want to combine their values, so that both signals are triggered simultaneously and their values ​​can come in any order:

 return [RACSignal merge:@[ [cacheSignal catchTo:[RACSignal empty]], [remoteSignal catchTo:[RACSignal empty]] ]; 

We ignore errors here because the error either stops both (since they are now merged). Error handling behavior has already been described above.

And despite the merge, using -takeUntil: in cacheSignal ensures that the value cannot be sent after remoteSignal .

Once again looking at the list of requirements, you can see the operators used to execute each of them:

  • [-doError:] remoteSignal should send its errors to remoteErrors
  • [-takeUntil:] cacheSignal should be canceled as soon as remoteSignal sends the value
  • [-catchTo:] Errors from any signal should not interrupt another
  • [+merge:] We want to combine the values ​​from cacheSignal and remoteSignal so that we still get the remote value after reading the cache.
+12
source share

All Articles