Mysterious Trousers

Quality software and amazing support since 2010

We’re a small team dedicated to making you happy. We achieve this through synergizing analytics and best-of-breed social blah blah blah hard work and honest-to-goodness customer service.

Blocks: A Case Against Protocol Delegation

Adam claimed the other day that in every situation he could think of, blocks could replace protocol delegation in Objective-C. I had never thought about this and almost dismissed it as soon as I heard it, but I’ve thought about it a lot the past few days. I wrote an experimental, drop-in replacement for UITableView called MTBlockTableView that demonstrates this idea. There’s 3 particular situtations where I use delegates the most, and I’d like to expand on each of them and the pros and cons of block versus protocol delegation.

UITableView

In MTBlockTableView, we took the typical delegate implementation and used blocks for each delegate method. Consider the difference from the snippets below:

Delegate Approach

// MyViewController.h

@interface MyViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>

@property (strong, nonatomic) UITableView *tableView;

@end



// MyViewController.m

@implementation

- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    ...
}

- (UITableViewCell*) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    ...
}

@end

Blocks Approach

// MyViewController.h

@interface MyViewController : UIViewController // No need to conform to protocols

@property (strong, nonatomic) MTBlockTableView *tableView;

@end



// MyViewController.m

@implementation

- (void) viewDidLoad {
    [_tableView setNumberOfRowsInSectionBlock:^NSInteger(UITableView *tableView, NSInteger section) {
        ...
    }];
    
    [_tableView setCellForRowAtIndexPathBlock:^UITableViewCell *(UITableView *tableView, NSIndexPath *indexPath) {
        ...
    }];
}


@end

Behind-the-scenes, MTBlockTableView is its own delegate and data source and it simply calls the corresponding blocks for each of the delegate methods.

However, in this particular example where the view controller is the delegate of only a single UITableView, you kind of break even, if you ask me. They’re both pretty much the same in terms of lines of code, and it mostly comes down to taste. With blocks, there’s no conforming to protocols which can be nice, but you also don’t get the compiler-enforced @required/@optional protocol method niceties. But let’s take another example.

UIAlertView

Better uses for blocks can be found when you have multiple objects of the same type using the same object as their delegate. UIAlertView comes to mind, where just one works fine:

// MyViewController.m

@implementation MyViewController

- (void) askForFirstName {
    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"First Name"
                                                        message:@"What is your first name?"
                                                       delegate:self
                                              cancelButtonTitle:@"Cancel"
                                              otherButtonTitles:@"OK", nil];
    
    alertView.alertViewStyle = UIAlertViewStylePlainTextInput;
    
    [alertView show];
}

- (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
    [_networkManager submitFirstNameToServer:[alertView textFieldAtIndex:0].text]];
}

@end

But what if you have two alert views? You could create distinct objects, each to be separate delegates (WAY too much work for something as simple as UIAlertView ought to be), or you could rig up something like this:

// MyViewController.m

#define ALERT_VIEW_FIRST_NAME_TAG 0
#define ALERT_VIEW_LAST_NAME_TAG 1

@implementation MyViewController

- (void) askForFirstName {
    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"First Name"
                                                        message:@"What is your first name?"
                                                       delegate:self
                                              cancelButtonTitle:@"Cancel"
                                              otherButtonTitles:@"OK", nil];
    
    alertView.alertViewStyle = UIAlertViewStylePlainTextInput;
    alertView.tag = ALERT_VIEW_FIRST_NAME_TAG;
    
    [alertView show];
}

- (void) askForLastName {
    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Last Name"
                                                        message:@"What is your last name?"
                                                       delegate:self
                                              cancelButtonTitle:@"Cancel"
                                              otherButtonTitles:@"OK", nil];
    
    alertView.alertViewStyle = UIAlertViewStylePlainTextInput;
    alertView.tag = ALERT_VIEW_LAST_NAME_TAG;
    
    [alertView show];
}

- (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
    if (alertView.tag == ALERT_VIEW_FIRST_NAME_TAG) {
        [_networkManager submitFirstNameToServer:[alertView textFieldAtIndex:0].text]];
    } else if (alertView.tag == ALERT_VIEW_LAST_NAME_TAG) {
        [_networkManager submitLastNameToServer:[alertView textFieldAtIndex:0].text]];
    }

}

@end

But then you’re getting crazy. I don’t like to get crazy. Consider a block-based approach:

- (void) askForFirstName {
    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"First Name"
                                                        message:@"What is your first name?"
                                                       delegate:self
                                              cancelButtonTitle:@"Cancel"
                                              otherButtonTitles:@"OK", nil];
    
    alertView.alertViewStyle = UIAlertViewStylePlainTextInput;
    
    [alertView setClickedButtonAtIndexBlock: ^(UIAlertView *alertView, NSInteger buttonIndex) {
        [_networkManager submitFirstNameToServer:[alertView textFieldAtIndex:0].text]];
    }];
    
    [alertView show];
}

- (void) askForLastName {
    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Last Name"
                                                        message:@"What is your last name?"
                                                       delegate:self
                                              cancelButtonTitle:@"Cancel"
                                              otherButtonTitles:@"OK", nil];
    
    alertView.alertViewStyle = UIAlertViewStylePlainTextInput;

    [alertView setClickedButtonAtIndexBlock: ^(UIAlertView *alertView, NSInteger buttonIndex) {
        [_networkManager submitLastNameToServer:[alertView textFieldAtIndex:0].text]];
    }];
    
    [alertView show];
}

Now each UIAlertView is in charge of calling its own, specific finished code, as it should be. Again, behind-the-scenes it’s just conforming to its own protocol and each delegate method is calling its corresponding block. This is an out-right win as far as I’m concerned. UIAlertViews are meant to be quick-and-easy, but that moment when you realize you have to conform to a protocol to respond to user action is always a brutal one, and having multiple alert views gives me hives. Fewer lines of code and ideas are grouped together logically.

I recently tried using RKClient, an HTTP request client, from the RestKit framework for an open source project and ran into this same problem. You tell the client to fetch JSON at some URL and it calls a delegate method when it’s done. What if you’re making multiple requests, and you want to handle the JSON differently for each request? You’re hosed. Delegates and protocols break down in this situation. I ended up using SVHTTPRequest instead, which uses blocks instead of delegates, and it’s really quite elegant.

(To be fair, I was using RKClient in an unorthodox way, I wasn’t using the object mapping that’s meant to be used with RestKit, which would have solved this particular problem, but you get the idea.)

This can also apply to our previous example where you might want one view controller to control two trivial table views. Rare, perhaps, but still a flexibility bonus that a block-based approach has over protocols.

One more example.

Modal View Controllers

Displaying modal view controllers usually goes something like this for me, where MySourceViewController presents MyDestinationViewController:

// MySourceViewController.h

#import "MyDestinationViewController.h"

@interface MySourceViewController : UIViewController 
@end

// MySourceViewController.m

#import "MySourceViewController.h"

@implementation MySourceViewController

- (IBAction)settingsButtonTapped:(id)sender {
    MyDestinationViewController *controller = [[MyDestinationViewController alloc] init];
    
    controller.delegate = self;

    [self presentModalViewController:controller animated:YES];
}

- (void) destinationViewControllerDidFinish:(MyDestinationViewController *)controller {
    [self dismissModalViewControllerAnimated:YES];
}

@end

// MyDestinationViewController.h

@protocol MyDestinationViewControllerDelegate;



@interface MyDestinationViewController : UIViewController

@property (nonatomic, weak) id delegate;

@end



@protocol MyDestinationViewControllerDelegate 

- (void) destinationViewControllerDidFinish:(MyDestinationViewController *)controller;

@end

// MyDestinationViewController.m

#import "MyDestinationViewController.h"

@implementation MyDestinationViewController

- (IBAction) doneButtonTapped:(id)sender {
    [_delegate destinationViewControllerDidFinish:self];
}

@end

Which is all fine and well, but in this situation you are still looking to do something quite simple. That’s a fair bit of code (declaring the protocol, conforming to it, etc) and blocks provide something a little more succinct:

// MySourceViewController.h

#import "MyDestinationViewController.h"

@interface MySourceViewController : UIViewController
@end

// MySourceViewController.m

#import "MySourceViewController.h"

@implementation MySourceViewController

- (IBAction)settingsButtonTapped:(id)sender {
    MyDestinationViewController *controller = [[MyDestinationViewController alloc] init];
    
    [controller setFinishedCallback:^(MyDestinationViewController *controller) {
        [self dismissModalViewControllerAnimated:YES];
    }];

    [self presentModalViewController:controller animated:YES];
}

@end

// MyDestinationViewController.h

@interface MyDestinationViewController : UIViewController

@property (nonatomic, strong) void (^finishedCallback)(MyDestinationViewController *controller);

@end

// MyDestinationViewController.m

#import "MyDestinationViewController.h"

@implementation MyDestinationViewController

- (IBAction) doneButtonTapped:(id)sender {
    if (_finishedCallback) {
        _finishedCallback(self);
    }
}

@end

Again, a huge win. No need to define a protocol, or conform to it. You handle both the presenting and the dismissal all at once. It’s much more elegant, natural, and pleasurable to code this way (once you get over the ugly block syntax), having written code using this convention for about a week now.

Adam and I feel this is the next natural progression of the delegate design pattern. Protocols were created because smart guys with beards realized that in certain circumstances, objects didn’t need to know what type another object was, just that it implemented certain methods. This was (and still is to an extent) very expressive, and is a key feature of why UIKit trumps many other graphical frameworks. But even that is overkill sometimes. In the last two situations I discussed above, the object doesn’t even need to know about the object at all, just that it can call a method.

We’re not saying do away with delegates altogether since in certain situations, like with UITableView, blocks are not an all-out win (though what if you wanted one view controller to control two table views?). But we are saying some of the UIKit framework, such as UIAlertView, would be much more elegant and expressive if we could use blocks in place of delegates. What do you think? Let us know at @mystrou on Twitter.

© 2013 Mysterious Trousers, LLC. All Rights Reserved. Background photo by hsld