Block Delegates

Often I find myself implementing the same delegate protocol over and over. Consider, for example, the Facebook SDK for iOS. I’d like to make lots of requests to the Graph API, and not have a single callback method request:didLoad: in a single class that looks like:

-(void)request:(FBRequest*)request didLoad:(id)result
{
    if (request == mainRequest)
    {
        // handle result
    }
    else if (request == subRequest1)
    {
        // handle result
    }
    else if (request == anotherRequest)
    {
        // handle result
    }
    //... far too many if blocks for my liking
}

This happens because we use one class as the delegate for all the requests. The alternative is to implement multiple classes, but then the handling code becomes spread across, well, multiple classes, which is yucky.

So what to do? Well (this has probably been done before but) my solution is to implement a delegate class once, and for each request, instantiate one, and pass it implementations using block properties. I will call this pattern Block Delegates. It goes like this.

First, set up a skeleton delegate, which implements the delegate protocol and has block properties for each delegate method that might be called:

@interface FBRequestBlockDelegate : NSObject {}
@property (nonatomic, copy) void(^requestLoading)(FBRequest*);
@property (nonatomic, copy) void(^requestDidReceiveResponse)(FBRequest*,NSURLResponse*);
@property (nonatomic, copy) void(^requestDidFailWithError)(FBRequest*,NSError*);
@property (nonatomic, copy) void(^requestDidLoad)(FBRequest*,id);
@property (nonatomic, copy) void(^requestDidLoadRawResponse)(FBRequest*,NSData*);
@end

Then use an implementation like this:

@implementation FBRequestBlockDelegate
@synthesize requestDidFailWithError, requestDidLoad, requestLoading, requestDidLoadRawResponse, requestDidReceiveResponse;
- (void)requestLoading:(FBRequest*)request
{
    if (self.requestLoading) self.requestLoading(request);
}
- (void)request:(FBRequest*)request didReceiveResponse:(NSURLResponse*)response
{
    if (self.requestDidReceiveResponse) self.requestDidReceiveResponse(request, response);
}
- (void)request:(FBRequest*)request didFailWithError:(NSError*)error
{
    if (self.requestDidFailWithError) self.requestDidFailWithError(request, error);
}
- (void)request:(FBRequest*)request didLoad:(id)result
{
    if (self.requestDidLoad) self.requestDidLoad(request,result);
}
- (void)request:(FBRequest*)request didLoadRawResponse:(NSData*)data
{
    if (self.requestDidLoadRawResponse) self.requestDidLoadRawResponse(request,data);
}
-(void)dealloc {
    self.requestDidFailWithError = nil;
    self.requestDidLoad = nil;
    self.requestDidLoadRawResponse = nil;
    self.requestDidReceiveResponse = nil;
    self.requestLoading = nil;
    [super dealloc];
}
@end

Now you can bundle the delegate code right next to the request. It only has to handle the current case, so no massive sequence of unnecessary if-statements.

FBRequestBlockDelegate *delegate = [FBRequestBlockDelegate new];
delegate.requestDidLoad = ^(FBRequest *request, id result) {
    // Process this particular request
    [delegate release];
};

[self.facebook requestWithGraphPath:@"some path" 
                        andDelegate:delegate];

Don’t forget that delegates aren’t usually retained by the class it is a delegate of, so a good idea is to release the delegate at the end of the last that will be called block.