Adding block callbacks to the Facebook iOS SDK

July 19th, 2011 Posted by: - posted under:Tutorials

If you are like me and love using blocks over delegates, then these code snippets will come in handy. After learning about blocks I have come to love to use them, especially using them for callbacks rather than using the old protocol/delegate methodology. These code snippets will allow you to use blocks as callbacks over delegates.

In a previous post Facebook SDK – Posting to User News Feed I showed how to post various types of status’s to the logged in users news feed. These code snippets have been added to a forked version of the official Facebook for iOS SDK. A sample of how to use the new block callbacks can be found within the sample project from the last post.

For more information on blocks, you can refer to the Apple document discussing blocks in more detail.

Step 1: Modifying FBRequest Class for new callbacks
First we will modify the FBRequest class. There are a few simple modifications that we need to create. Within the FBRequest.h file add the following:

@property (copy) void (^FBRequestCallback)(FBRequest*, id, NSError*);
@property (nonatomic, assign)  BOOL usesBlockCallback;
 
+ (FBRequest*) getRequestWithParams:(NSMutableDictionary *)params
                         httpMethod:(NSString *)httpMethod
                           callback:(void(^)(FBRequest *request, id result, NSError *error)) _block
                         requestURL:(NSString *)url;

In the FBRequest.m file add the following code:

@synthesize FBRequestCallback = _FBRequestCallback;
@synthesize usesBlockCallback;
 
+ (FBRequest*) getRequestWithParams:(NSMutableDictionary *)params
                         httpMethod:(NSString *)httpMethod
                           callback:(void(^)(FBRequest *request, id result, NSError *error)) _block
                         requestURL:(NSString *)url
{
    FBRequest* request = [[[FBRequest alloc] init] autorelease];
    request.delegate = nil;
    request.url = url;
    request.httpMethod = httpMethod;
    request.params = params;
    request.connection = nil;
    request.responseText = nil;
    request.FBRequestCallback = _block;
 
    return request;
}

In the FBRequest class we are duplicating what they already have in the provided method but swapping out the delegate with a block. Out of personal preference I went with a single callback versus having an error and result callback. If there is no error we will be returning nil for the error parameter.

I also added a boolean property that will be used to determine which callback to use when processing the data when it has been returned from the Facebook servers.

Moving onto the – (void)handleResponseData:(NSData *)data; Method we need to add logic to pass the result request data onto the block callback.

- (void)handleResponseData:(NSData *)data {
  if ([_delegate respondsToSelector:
      @selector(request:didLoadRawResponse:)]) {
    [_delegate request:self didLoadRawResponse:data];
  }
 
    if (usesBlockCallback) {
        NSError* error = nil;
        id result = [self parseJsonResponse:data error:&error];
        _FBRequestCallback(self, result, error);
    }
    else if ([_delegate respondsToSelector:@selector(request:didLoad:)] ||
             [_delegate respondsToSelector:@selector(request:didFailWithError:)])
    {
        NSError* error = nil;
        id result = [self parseJsonResponse:data error:&error];
 
        if (error) {
            [self failWithError:error];
        }
        else if ([_delegate respondsToSelector:@selector(request:didLoad:)]) {
            [_delegate request:self didLoad:(result == nil ? data : result)];
        }
 
    }
}

We are using the same logic as the standard delegate methodology, except we do not need to make a separate delegate call if there is an error. Within the block we have included result and an NSError parameter. That way you can handle the error logic within the same callback.

Finally within the dealloc method we need to release the block. Since we are using the block in objective-c we can simply use [_FBRequestCallback release]; You can also use Block_release(_FBRequestCallback); but since we are using it within Objective-C we can use the standard way of releasing an object.

- (void)dealloc {
    [_FBRequestCallback release];
    [_connection cancel];
    [_connection release];
    [_responseText release];
    [_url release];
    [_httpMethod release];
    [_params release];
    [super dealloc];
}

Step 2: Adding request methods with block callbacks

We want to include two new methods to the Facebook.h class. One method is for graph API requests, and one is for the the old REST API.

In the Facebook.h add the following two methods:

- (void) requestWithGraphPath:(NSString *) _graphPath
                       params:(NSMutableDictionary*) _params
                       method:(NSString*) _method
                     callback:(void(^)(FBRequest *request, id result, NSError *error)) _block;
 
- (void) requestWithMethodName:(NSString *) _methodName
                     andParams:(NSMutableDictionary *) _params
                 andHttpMethod:(NSString *) _method
                      callback:(void(^)(FBRequest *request, id result, NSError *error)) _block;

In the Facebook.m file add the following two method implementations:

- (void) requestWithGraphPath:(NSString *) _graphPath
                       params:(NSMutableDictionary*) _params
                       method:(NSString*) _method
                     callback:(void(^)(FBRequest *request, id result, NSError *error)) _block
{
    NSString * fullURL = [kGraphBaseURL stringByAppendingString:_graphPath];
    [_params setValue:@"json" forKey:@"format"];
    [_params setValue:kSDK forKey:@"sdk"];
    [_params setValue:kSDKVersion forKey:@"sdk_version"];
    if ([self isSessionValid]) {
        [_params setValue:self.accessToken forKey:@"access_token"];
    }
 
    [_request release];
    //modify request to have block
    _request = [[FBRequest getRequestWithParams:_params
                                     httpMethod:_method
                                       callback:_block
                                     requestURL:fullURL] retain];
    [_request connect];
}
 
- (void) requestWithMethodName:(NSString *) _methodName
                     andParams:(NSMutableDictionary *) _params
                 andHttpMethod:(NSString *) _method
                      callback:(void(^)(FBRequest *request, id result, NSError *error)) _block
{
    NSString * fullURL = [kRestserverBaseURL stringByAppendingString:_methodName];
    [_params setValue:@"json" forKey:@"format"];
    [_params setValue:kSDK forKey:@"sdk"];
    [_params setValue:kSDKVersion forKey:@"sdk_version"];
    if ([self isSessionValid]) {
        [_params setValue:self.accessToken forKey:@"access_token"];
    }
 
    [_request release];
    //modify request to have block
    _request = [[FBRequest getRequestWithParams:_params
                                     httpMethod:_method
                                       callback:_block
                                     requestURL:fullURL] retain];
    [_request connect];
 
}

Once again we are duplicating the previously implemented methods by Facebook. The only difference is changing the request method. As you notice we are now using the methods we implemented in the FBRequest class.

Now that we have everything implemented that we need in the Facebook SDK we can use our awesome new block callbacks! The following snippet will return the logged in users friends!

    NSMutableDictionary *params = [[[NSMutableDictionary alloc] init] autorelease];
    [facebook requestWithGraphPath:@"me/friends"
                                    params:params
                                    method:@"GET"
                                  callback:^(FBRequest *request, id result, NSError *error)
    {
        NSLog(@"friends %@", result);
        if ([result isKindOfClass:[NSDictionary class]]) {
            friendsList = [[result objectForKey:@"data"] retain];
            [table reloadData];
        }
 
    }];