Adding a ReactiveCocoa networking layer

March 20, 2014

I started using some ReactiveCocoa in my network layer after learning more about it recently. There are several good articles including one by Chris Trott titled How I Wrote Vinylogue for iOS With ReactiveCocoa. This is a fantasic geek article and includes details about how he incorporated ReactiveCocoa into the different layers of Vinylogue. The network approach I took is adapted from his and similar to this one by Prabir Shrestha.

Here’s the basic setup I am using:

- (RACSignal *)signalForURLRequest:(NSURLRequest *)request {
    RACReplaySubject *subject = [RACReplaySubject subject];
    
    AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
    [operation setResponseSerializer:[AFHTTPResponseSerializer serializer]];
    [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
        [subject sendNext:responseObject];
        [subject sendCompleted];
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        [subject sendError:error];
        [subject sendCompleted];
    }];
    
    [[NSOperationQueue mainQueue] addOperation:operation];
    
    return [subject deliverOn:[RACScheduler scheduler]];
}

First a RACReplaySubject is created followed by an asynchronous networking operation, in this case a AFHTTPRequestOperation. The completion and error blocks of the operation are configured and the operation added to an NSOperationQueue. Finally, the RACReplaySubject is returned after being delivered on the proper scheduler. Within the completion and error blocks for the async call, the response or error objects are sent alonge the RACReplaySubject, followed by a completion signal.

The next step is to call signalForURLRequest: with a URL request and listen for response signals. Here the map: function is used to manipulate the response returned on the signal into a model object.

- (RACSignal *)passInfoForURL:(NSURL *)url {
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    RACSignal *signal = [[self signalForURLRequest:request] map:^id(id responseObj) {
        NSMutableDictionary *passObj = [NSMutableDictionary dictionaryWithDictionary:responseObj];
        PASPass *pass = [MTLJSONAdapter modelOfClass:[PASPass class] fromJSONDictionary:passObj error:nil];
        return pass;
    }];
    
    return signal;
}

This setup works nicely for my use case which requires information from multiple URL endpoints before assembling complete model data. ReactiveCocoa can hitch onto multiple asynchronous operations and signal during operation state changes. A signal is generated as each operation finishes, which allows for the responses to be handled individually. A final signal is generated when all operations are complete.

- (RACSignal *)refreshPassInfoSignal {
    NSMutableArray *signals = [NSMutableArray array];
    [self.passes enumerateObjectsUsingBlock:^(PASPass *pass, NSUInteger idx, BOOL *stop) {
        NSDictionary *info = @{@"referenceURL": pass.referenceURL, @"title": pass.title};
        RACSignal *signal = [self.dataRetriever passInfoForURL:pass.referenceURL info:info];
        [signals addObject:signal];
    }];
    
    RACReplaySubject *subject = [RACReplaySubject subject];
    
    @weakify(self);
    // temporarily keep track of new passes as they come in
    NSMutableArray *passes = [NSMutableArray array];
    [[RACSignal merge:signals] subscribeNext:^(PASPass *pass) {
        // add a new pass object
        [passes addObject:pass];
    } completed:^{
        @strongify(self);
        // when complete, save all the passes accumulated.
        self.passes = [NSArray arrayWithArray:passes];
        [subject sendCompleted];
    }];
    
    return [subject deliverOn:[RACScheduler mainThreadScheduler]];
}

The method above shows up in my data source object. Here an array of RACSignals is built with one signal for each URL containing model data. The individual signals are merged together and new signal is returned, one that fires when all the individual signals have finished.

ReactiveCocoa is a new addition to my workflow and there is much I am still learning. I haven’t delved into replacing much of my UI layer with ReactiveCocoa but there are some promising approaches. One thing is note is that ReactiveCocoa 3.0 is coming down the road and some of the techniques outlined above are slated to change.

The network stack described above is available in the following gist.