I am using ReactiveCocoa in an app which makes calls to remote web APIs. But before any thing can be retrieved from a given API host, the app must provide the user's credentials and retrieve an API token, which is then used to sign subsequent requests.
I want to abstract away this authentication process so that it happens automatically whenever I make an API call. Assume I have an API client class that contains the user's credentials.
// getThing returns RACSignal yielding the data returned by GET /thing.
// if the apiClient instance doesn't already have a token, it must
// retrieve one before calling GET /thing
RAC(self.thing) = [apiClient getThing];
How can I use ReactiveCocoa to transparently cause the first (and only the first) request to an API to retrieve and, as a side effect, safely store an API token before any subsequent requests are made?
It is also a requirement that I can use combineLatest:
(or similar) to kick off multiple simultaneous requests and that they will all implicitly wait for the token to be retrieved.
RAC(self.tupleOfThisAndThat) = [RACSignal combineLatest:@[ [apiClient getThis], [apiClient getThat]]];
Further, if the retrieve-token request is already in flight when an API call is made, that API call must wait until the retrieve-token request has completed.
My partial solution follows:
The basic pattern is going to be to use flattenMap:
to map a signal which yields the token to a signal that, given the token, performs the desired request and yields the result of the API call.
Assuming some convenient extensions to NSURLRequest
:
- (RACSignal *)requestSignalWithURLRequest:(NSURLRequest *)urlRequest {
if ([urlRequest isSignedWithAToken])
return [self performURLRequest:urlRequest];
return [[self getToken] flattenMap:^ RACSignal * (id token) {
NSURLRequest *signedRequest = [urlRequest signedRequestWithToken:token];
assert([urlRequest isSignedWithAToken]);
return [self requestSignalWithURLRequest:signedRequest];
}
}
Now consider the subscription implementation of -getToken
.
- In the trivial case, when the token has already been retrieved, the subscription yields the token immediately.
- If the token has not been retrieved, the subscription defers to an authentication API call which returns the token.
- If the authentication API call is in flight, it should be safe to add another observer without causing the authentication API call to be repeated over the wire.
However I'm not sure how to do this. Also, how and where to safely store the token? Some kind of persistent/repeatable signal?