Better asynchronous control flow with Objective-C blocks

bromanko picture bromanko · Jun 6, 2012 · Viewed 10.2k times · Source

I'm using AFNetworking for asynchronous calls to a web service. Some of these calls must be chained together, where the results of call A are used by call B which are used by call C, etc.

AFNetworking handles results of async calls with success/failure blocks set at the time the operation is created:

NSURL *url = [NSURL URLWithString:@"http://api.twitter.com/1/statuses/public_timeline.json"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
    NSLog(@"Public Timeline: %@", JSON);
} failure:nil];
[operation start];

This results in nested async call blocks which quickly becomes unreadable. It's even more complicated when tasks are not dependent on one another and instead must execute in parallel and execution depends on the results of all operations.

It seems that a better approach would be to leverage a promises framework to clean up the control flow.

I've come across MAFuture but can't figure out how best to integrate it with AFNetworking. Since the async calls could have multiple results (success/failure) and don't have a return value it doesn't seem like an ideal fit.

Any pointers or ideas would be appreciated.

Answer

Tal Bereznitskey picture Tal Bereznitskey · Dec 11, 2012

I created a light-weight solution for this. It's called Sequencer and it's up on github.

It makes chaining API calls (or any other async code) easy and straightforward.

Here's an example of using AFNetworking with it:

Sequencer *sequencer = [[Sequencer alloc] init];

[sequencer enqueueStep:^(id result, SequencerCompletion completion) {
    NSURL *url = [NSURL URLWithString:@"https://alpha-api.app.net/stream/0/posts/stream/global"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
        completion(JSON);
    } failure:nil];
    [operation start];
}];

[sequencer enqueueStep:^(NSDictionary *feed, SequencerCompletion completion) {
    NSArray *data = [feed objectForKey:@"data"];
    NSDictionary *lastFeedItem = [data lastObject];
    NSString *cononicalURL = [lastFeedItem objectForKey:@"canonical_url"];

    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:cononicalURL]];
    AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
    [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
        completion(responseObject);
    } failure:nil];
    [operation start];
}];

[sequencer enqueueStep:^(NSData *htmlData, SequencerCompletion completion) {
    NSString *html = [[NSString alloc] initWithData:htmlData encoding:NSUTF8StringEncoding];
    NSLog(@"HTML Page: %@", html);
    completion(nil);
}];

[sequencer run];