Sapan Diwakar

Software developer

Follow me on Twitter Check out my code on GitHub View some of my designs on Dribbble Take a look at my Linked In profile

PromiseKit: Promises for iOS

Most of the apps today have to deal with lot of asynchronous stuff. If you do a lot of asynchronous NSURLRequests, Restkit's getObjectsAtPath:parameters:success:failure:, or even simple asynchronous UI actions (e.g. UIAlertView and UIActionSheet), I am sure you often see the code becoming ugly, wrapped inside asynchronous boilerplates, deep nesting of blocks etc. There's a very easy and delightful way of dealing with these, PromiseKit. If you have worked with web development, you might already be familiar with promises.

Let's see a few examples of PromiseKit. First include it in the pod file

pod 'PromiseKit'  

With this, you can change

- (void)viewDidLoad {
    id rq = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://placekitten.com/320/320"]];

    void (^handleError)(id) = ^(NSString *msg){
        UIAlertView *alert = [[UIAlertView alloc] init… delegate:self …];
        [alert show];
    };

    [NSURLConnection sendAsynchronousRequest:rq completionHandler:^(id response, id data, id error) {
        if (error) {
            handle([error localizedDescription]);
        } else {
            UIImage *image = [UIImage imageWithData:data];
            if (!image) {
                handleError(@"Bad server response");
            } else {
                self.imageView.image = image;
            }
        }
    }];
}

- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
    // hopefully we won’t ever have multiple UIAlertViews handled in this class
    [self dismissViewControllerAnimated:YES];
}

to something as simple as this

- (void)viewDidLoad {
    [NSURLConnection GET:@"http://placekitten.com/320/320"].then(^(UIImage *image) {
        self.imageView.image = image;
    }).catch(^(NSError *error){
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:…];
        [alert promise].then(^{
            [self dismissViewControllerAnimated:YES];
        })
    });
}

See the use of then? It subscribes the completed callback for the NSURLConnection GET request and passes the results to a block. catch is where you go to whenever the promise fails.

Well, it's still just blocks and we will eventually land up into multiple nested levels, right? Not at all. You can chain promises, i.e. you can return promises from the then block and then listen to the completion of the promise from the next then in the chain.

UIActionSheet *sheet = [[UIActionSheet alloc] initWithTitle:…];  
[sheet promise].then(^(NSNumber *buttonIndex){
    int const size = (buttonIndex.intValue + 1) * 100;
    // We will return a promise
    return [NSURLConnection GET:@"http://placekitten.com/%d/%d", size, size];
}).then(^(UIImage *kittenImage){
    // NSURLRequest completed
    self.imageView = image;
});

And, you don't need an individual catch for each promise you return (although you can have as many as you want). When any promise in a chain fails, it invokes the first catch following the promise.

Using RestKit? Promises will work there too!

// Book model
+ (Promise *)getBooksForParams:(NSDictionary *)params {
    return [Promise new:^(PromiseResolver fulfiller, PromiseResolver rejecter) {
        [[RKObjectManager sharedManager] getObjectsAtPathForRouteNamed:kRestClientListBooksRouteName object:nil parameters:params success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
            fulfiller(PMKManifold(mappingResult.array));
        } failure:^(RKObjectRequestOperation *operation, NSError *error) {
            rejecter(error);
        }];
    }];
}

// In ViewController
[Book getBooksForParams:params].then(^(NSArray* books){
    ...
});