ReactiveCocoa signal for fetching data upon observation of authentication status

465 views Asked by At

Fairly new to ReactiveCocoa, I'm trying to build a signal that asynchronously fetches some resource from a remote API to which the client has to authenticate first. Authentication is handled by first getting a token from the API, and then passing it via some custom HTTP header for each subsequent request. However, the custom header might be set after the fetchResource signal is subscribed to, which in the current situation leads to an unauthenticated request. I guess I could actually build the request in the subscribeNext block of self.authenticationStatus, thus ensuring that the token will be set, but how could I handle the disposition of the signal then?

- (RACSignal *)fetchResource
{
    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        NSURLRequest *request = [self.requestSerializer
                                 requestWithMethod:@"GET"
                                 URLString:[[NSURL URLWithString:@"resource" relativeToURL:self.baseURL] absoluteString]
                                 parameters:nil error:nil];
        NSURLSessionDataTask *task = [self dataTaskWithRequest:request
                                      completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
            if (error) {
                [subscriber sendError:error];
            } else {
                [subscriber sendNext:responseObject];
                [subscriber sendCompleted];
            }
        }];

        // Actually trigger the request only once the authentication token has been fetched.
        [[self.authenticationStatus ignore:@NO] subscribeNext:^(id _) {
            [task resume];
        }];

        return [RACDisposable disposableWithBlock:^{
            [task cancel];
        }];
    }];
}
1

There are 1 answers

0
erikprice On
- (RACSignal *)fetchTokenWithCredentials:(Credentials *)credentials
{
    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {

        // Fetch the token and send it to `subscriber`.
        Token *t = ... ;
        [subscriber sendNext:t];

        return nil;

    }];
}

- (RACSignal *)fetchResourceWithToken:(Token *)token
{
    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {

        // Use `token` to set the request header. Then fetch
        // the resource and send it to `subscriber`. Basically
        // this part is what you already have.
        Resource *r = ... ;
        [subscriber sendNext:r];

        return nil;

    }];
}

In your view controller, present the modal authentication dialog if you don't have a valid token. When the user taps the "submit" button, do something like the following:

- (IBAction)handleAuthenticationSubmit:(id)sender
{
    Credentials *c = ... ;
    RACSignal *resourceSignal = [[[self fetchTokenWithCredentials:c]
            flattenMap:^(Token *t) {

                return [self fetchResourceWithToken:t];

            }]
            deliverOn:RACScheduler.mainThreadScheduler];

    [self rac_liftSelector:@selector(receiveResource:) withSignals:resourceSignal, nil];
}

- (void)receiveResource:(Resource *)resource
{
    [self.delegate authenticationController:self didReceiveResource:resource];
}