Skip to content

Instantly share code, notes, and snippets.

@nicklockwood
Last active December 28, 2015 04:59
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nicklockwood/7446228 to your computer and use it in GitHub Desktop.
Save nicklockwood/7446228 to your computer and use it in GitHub Desktop.
Why having a single success/error callback block is better than having separate ones

Suppose you have a network method that only uses a single block callback

typedef void (^Handler)(BOOL success, id response, NSError *error);

- (void)makeRequestWithHandler:(Handler)handler;

But you have to make this request dozens of times, and you want to reuse the same failure handler in most cases? Easy, just have a factory function that returns a block:

typedef void (^SuccessHandler)(id response);
typedef void (^ErrorHandler)(NSError *error);

Handler makeHandler(SuccessHandler successBlock, ErrorHandler errorBlock)
{
    return ^(BOOL success, id response, NSError *error)
    {
        if (success)
        {
            if (successBlock) successBlock(response);
        }
        else
        {
            if (errorBlock) errorBlock(error);
        }
    }
}

Now you can elegantly call your singler-handler function with seperate success/failure handlers whenever you feel like it:

[[MyNetworkClass sharedInstance] makeRequestWithHandler:makeHandler(^(id response) {

    //success code
    
}, ^(NSError *error) {

   //error code

})];

This also means that if you have a generic error handler, you can just change the error condition in your makeHandler function to:

if (errorBlock) errorBlock(error); else //generic error handling behaviour

And then to use this fallback, just call your network method as follows:

[[MyNetworkClass sharedInstance] makeRequestWithHandler:makeHandler(^(id response) {

    //success code
    
}, NULL)];

And this makeHandler method could even be built into the network library, with various standard implementations for your convenience.

So having a single callback gives you huge flexibility. Having seprate success/failure handlers built into the network method interface would force you to always specify two parameters; there's no easy way to make an equivalent factory function that takes a single handler block and splits it into two.

@nicklockwood
Copy link
Author

Disclaimer: I've not run any of the code above, and have no idea if it actually works.

@mindbrix
Copy link

My simple sanitiser method:

-(void)loadJSONFromURL:(NSURL *)url completion:( void(^)( NSDictionary *jsonDictionary, NSError *error ))completionBlock
{
    NSParameterAssert( url && completionBlock );

    NSURLRequest *request = [ NSURLRequest requestWithURL:url ];

    AFHTTPRequestOperation *operation = [[[ AFHTTPRequestOperation alloc ] initWithRequest:request ] autorelease ];

    operation.responseSerializer = [ AFJSONResponseSerializer serializer ];

    [ operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
     {
         completionBlock( responseObject, nil );

     } failure:^(AFHTTPRequestOperation *operation, NSError *error)
     {
         completionBlock( nil, error );
     }];

    [ operation start ];
}

@RuiAAPeres
Copy link

I am using the same approach Nigel.

@kylef
Copy link

kylef commented Nov 13, 2013

What about just doing:

[operation setCompletionBlock:^{
    self.error;
    self.responseObject;
}];

// note, use weak

OR

dispatch_block_t myRealHandler = ^{
    NSLog(@"Request succeeded or failed.");
}

[operation setCompletionBlockWithSuccess:(void ( ^ ) ( AFHTTPRequestOperation *operation , id responseObject )) {
    myRealHandler();
} failure:(void ( ^ ) ( AFHTTPRequestOperation *operation , NSError *error )) {
    myRealHandler();
}];

@RuiAAPeres
Copy link

@kylef I think the goal is to not repeat code (DRY principle). You are doing the same thing, on both handlers (success and failure)

@nicklockwood
Copy link
Author

@kylef those solutions take advantage of the fact that AFNetworking's request operation is a leaky abstraction of NSOperation. My gist makes no reference to AFNetworking. Another network library that didn't use NSOperation but still used the two-handler approach wouldn't neccesarily allow that solution.

Also, If I'm making a lot of requests, having to do that isn't very DRY. I could wrap all the request methods of the library with new methods that take a single handler of course, but if users have to do that, it rather defeats the point of designing a nice API in the first place.

@bvanderveen
Copy link

I encourage you all to look at ReactiveCocoa. All of the above approaches are effectively identical, a matter of taste. RAC is much more generally applicable across your entire program and by standardizing callback mechanisms to its design you can see rewards across your stack—eliminate that darn state!

@RuiAAPeres
Copy link

@bvanderveen The problem with ReactiveCocoa, is that you perfectly ok to use on your own stuff. In many places it's just plain impossible. The time it would take to new comers to understand: 1) Your app architecture 2) Work effectively with ReactiveCocoa 3) The company actually accepts the use of it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment