I have two network signals that I want to merge, but with some restrictions.
Let us call the network signals A and B. A does use AFNetworking to look up a resource in the cache and return any response for that request immediately. B also considers the cache, but can go to the remote server for revalidation of the response.
Ok, so what I want to do:
Request A:
Request B:
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 fulfill my requirements, so I wonder if anyone could help me with a better solution.
- (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
]];
}
This is a difficult question to answer, because your desired error handling is fundamentally incompatible with the RACSignal
API contract, which states that errors have exception semantics.
The only way to ignore but still care about errors is to redirect them elsewhere. In this example, I'll use a subject:
RACSubject *remoteErrors = [RACSubject subject];
… but you could also use a property or some other notification mechanism.
I'll continue to use the remoteSignal
and cacheSignal
you've given above, with some modifications. Here's the behavior we want from them:
remoteSignal
should send its errors to remoteErrors
cacheSignal
should be canceled as soon as remoteSignal
sends a valuecacheSignal
and remoteSignal
, so that we still get the remote value after the cache is readWith this in mind, let's 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];
The -doError:
and -finally:
control the remoteErrors
subject, fulfilling our first requirement above. Because we need to use remoteSignal
in more than one place (as you can sorta see in the list above), we use -replayLazily
to ensure that its side effects only occur once.
cacheSignal
is almost unchanged. We just need to use -takeUntil:
to ensure that it terminates when remoteSignal
sends a value (but not if it sends an error):
RACSignal *cacheSignal = [[[IssueWSRequest
instance]
issueWithId:issueId cachePolicy:NSURLRequestReturnCacheDataDontLoad]
takeUntil:remoteSignal];
Finally, we want to merge their values, so that both signals are started at the same time and their values can arrive in any order:
return [RACSignal merge:@[
[cacheSignal catchTo:[RACSignal empty]],
[remoteSignal catchTo:[RACSignal empty]]
];
We're ignoring errors here, because an error from either would terminate both (since they've been combined now). Our error handling behavior is already taken care of above.
And, despite the merge, using -takeUntil:
on the cacheSignal
ensures that it's impossible for it to send a value after remoteSignal
does.
Taking another look at the list of requirements, you can see the operators used to fulfill each one:
[-doError:]
remoteSignal
should send its errors to remoteErrors
[-takeUntil:]
cacheSignal
should be canceled as soon as remoteSignal
sends a value[-catchTo:]
Errors from either signal should not terminate the other[+merge:]
We want to merge the values from cacheSignal
and remoteSignal
, so that we still get the remote value after the cache is readIf you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With