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 of the official Facebook for iOS SDK. A sample of how to use the new block callbacks can be found within the from the last post.

    For more information on blocks, you can refer to the 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];
            }
     
        }];