Blocks Rock – A Cocoa Asynchronous NSURLConnection block example

So I heard that blocks were one of the new features of 4.1 and I decided to give it a try. And it’s awesome! Within half an hour I’d solved a problem that I’d had to make much more complicated implementations to solve previously, and I’m so excited I decided I’d share the code.

Problem: NSURLConnection asynchronous connections are ugly. If you want to have one controller make multiple different calls you have to make unique delegates for each call or have some convoluted way for the connection manager to tell one delegate from another. It’s a real mess.

Solution: BLOCKS!

Step 1: Create NSURLConnection block extension that allows for calling async methods from class API like you would sync method.


//
//  NSURLConnection-block.h
//
//  Created by Kevin Lohman on 9/12/10.
//  Copyright 2010 Logic High Software. All rights reserved.
//  Free to use in your code commercial or otherwise, as long as you leave this comment block in
// http://blog.logichigh.com/2010/09/12/cocoa-blocks/

#import 

@interface NSURLConnection (block)
#pragma mark Class API Extensions
+ (void)asyncRequest:(NSURLRequest *)request success:(void(^)(NSData *,NSURLResponse *))successBlock_ failure:(void(^)(NSData *,NSError *))failureBlock_;
@end

and

#import "NSURLConnection-block.h"

@implementation NSURLConnection (block)

#pragma mark API
+ (void)asyncRequest:(NSURLRequest *)request success:(void(^)(NSData *,NSURLResponse *))successBlock_ failure:(void(^)(NSData *,NSError *))failureBlock_
{
	[NSThread detachNewThreadSelector:@selector(backgroundSync:) toTarget:[NSURLConnection class]
						   withObject:[NSDictionary dictionaryWithObjectsAndKeys:
									   request,@"request",
									   successBlock_,@"success",
									   failureBlock_,@"failure",
									   nil]];
}

#pragma mark Private
+ (void)backgroundSync:(NSDictionary *)dictionary
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	void(^success)(NSData *,NSURLResponse *) = [dictionary objectForKey:@"success"];
	void(^failure)(NSData *,NSError *) = [dictionary objectForKey:@"failure"];
	NSURLRequest *request = [dictionary objectForKey:@"request"];
	NSURLResponse *response = nil;
	NSError *error = nil;
	NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
	if(error)
	{
		failure(data,error);
	}
	else
	{
		success(data,response);
	}
	[pool release];
}


@end

Now just import your new class and make your async call! AWESOME!

	[NSURLConnection asyncRequest:request
						  success:^(NSData *data, NSURLResponse *response) {
							  NSLog(@"Success!");
						  }
						  failure:^(NSData *data, NSError *error) {
							  NSLog(@"Error! %@",[error localizedDescription]);
						  }];

14 Comments

  • By Kevin Lohman, September 20, 2010 @ 4:04 pm

    I should comment… seems blocks are a little more work then I’d originally hoped. While this will work in some places, NSThread is a lot of overhead, and using a Singleton connection manager is probably a more efficient way to go. Additionally, when you are using values in blocks, it’s important to make sure they are in scope (make copies of parameter variables) and to check for the existence of those variables before calling them. Otherwise nasty, hard to track errors can occur with nested blocks.

  • By Kevin Lohman, September 21, 2010 @ 6:24 pm

    Okay… additionally, I’ve found it’s important to copy your blocks. parameterBlock = [[parameterBlock copy] autorelease];
    Especially if you plan on using multiple nested blocks.

  • By Hans Pinckaers, November 7, 2010 @ 9:56 am

    Awesome thanks! You could also use GCD.

  • By Tom Bradley, January 29, 2011 @ 3:36 am

    Hi, this is a great idea! It tidies up the code so much!

    However, I’ve tidied it up a bit further. Instead of creating an NSThread to do the work just use Grand Central Dispatch. The method below is all you need.

    + (void)asyncRequest:(NSURLRequest *)request success:(void(^)(NSData *,NSURLResponse *))successBlock_ failure:(void(^)(NSData *,NSError *))failureBlock_
    {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSURLResponse *response = nil;
    NSError *error = nil;
    NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];

    if (error) {
    failureBlock_(data,error);
    } else {
    successBlock_(data,response);
    }

    [pool release];
    });
    }

  • By Remy Demarest, June 4, 2011 @ 1:11 pm

    @Kevin Lohman: actually the blocks should be copied in only one case (but that’s a big case): if the block is supposed to be live beyond the scope in which it’s created (it’s the case in asynchronous operations like here), the block MUST be copied to the heap or you will get a crash for sure. And the code in this blog post is bound to crash.

    Here is the corrections, it’s at the dictionary creation:
    [NSDictionary dictionaryWithObjectsAndKeys:
    request,@”request”,
    [[successBlock_ copy] autorelease],@”success”,
    [[failureBlock_ copy] autorelease],@”failure”,
    nil]];

    Short explanation: blocks are allocated on the stack for optimization purpose, when the function returns the stack is cleaned up and the block dies out. The block has to be moved to the heap to exist beyond the scope, at that point it works like any other ObjC objects.

    When the block is still on the stack, -retain doesn’t do anything to the object, -retain must always return the receiver, that’s part of the semantic, thus sending retain to a block (like what NSDictionary is doing) won’t copy it to the heap. It has to be copied explicitly, you can then autorelease it because the dictionary will retain the block, in this case it works properly because -retain on a heap block increase its retain count just like normal ObjC objects.

  • By BadPirate, June 24, 2011 @ 2:40 pm

    @Remy – Even better now… Using ARC (Auto retain / release, see XCode 4.2 documentation) the copy / autorelease nonsense is no longer required at all! (Celebration!)

  • By Julius, December 28, 2011 @ 9:49 pm

    seems like your async call cannot be cancel

  • By 0x8badf00d, February 3, 2012 @ 7:46 am

    Misleading title. Making a Synchronous network call on background thread doesn’t make it asynchronous. You are blocking thread that makes synchronous request and waits for response, if the server takes several minutes to reply back your thread is blocked until then.

  • By Marcelo Cantos, February 8, 2012 @ 3:58 pm

    The better solution would have been to wrap NSURLConnection’s asynchronous model in blocks.

  • By 0xSina, April 17, 2012 @ 12:32 pm

    As Marcelo Cantos stated, much better solution is to wrap this in NSURLConnections’ asynchronous model. YOu get more functionality, and speed. i wrote URLConnection, it’s easy to use and add to your project. Find the link to it below:

    http://messagesenttodeallocatedinstance.wordpress.com/2012/04/10/nsurlconnection-with-blocks/

  • By BadPirate, June 26, 2013 @ 9:18 am

    FYI — Code here is mostly to demonstrate blocks, and is actually a really terrible way to do async connection handling (it spawns a new thread for EACH connection, bad memory management). A better solution is to use NSOperationQueue to handle it… or better still check out Apple’s NSURLSession (iOS developers only until iOS 7 goes live).

  • By BadPirate, June 26, 2013 @ 9:19 am

    Absolutely. :) This was an example of making your own method with Blocks… At the time there wasn’t much on the web about them, but Apple is thankfully using it more and more (though the syntax can still get confusing and hard to write off the top of your head)

Other Links to this Post

  1. Raise Asynchronous HTTP Requests in iOS « Anthony Works — June 22, 2013 @ 4:53 pm

  2. [iOS] Objective-C , block | Monster Oasis — March 5, 2014 @ 12:30 am

RSS feed for comments on this post. TrackBack URI

Leave a comment

WordPress Themes