35

Is it advisable to wrap up NSUrlConnection in a gcd style blocks and run it on a low_priority queue?

I need to ensure that my connections are not happening on the main thread and the connections need to be asynchronous. I also need several simultaneous requests to go at once.

If I go the gcd route, I'm not sure which thread the NSUrlConnectionDelegate methods get invoked on.

NSURLConnection relies on delegates so once the connection is complete, whatever wrapper class that handles it will need to invoke its caller. I'm not certain how to deal with all of the various callbacks that are invoked when the connection work starts up/finishes:

- (void)connection:(NSURLConnection *)aConnection didReceiveResponse:(NSURLResponse *)response;
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)incrementalData;
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
- (void)connectionDidFinishLoading:(NSURLConnection *)connection;

Should I just call the synchronous versions but wrapped in gcd blocks? And if I want to cancel a call, use 'dispatch_suspend'?

dispatch_async(queue,^{
      NSString* result = [self mySynchronousHttp:someURLToInvoke];
      });

// If I need to cancel
dispatch_suspend(queue);
Jonas Anderson
  • 1,987
  • 4
  • 24
  • 28

3 Answers3

53

I recommend you to see WWDC Sessions about network application in iPhone OS.

  • WWDC 2010 Session 207 - Network Apps for iPhone OS, Part 1
  • WWDC 2010 Session 208 - Network Apps for iPhone OS, Part 2

The lecturer said

"Threads Are Evil™"

for network programming and recommended to use asynchronous network programming with RunLoop. Use background thread (Grand Central Dispatch Concurrent Queue) for thread-safe data processing, not for network programming.

By the way, Blocks and Grand Central Dispatch sessions are also worth to see.

  • WWDC 2010 Session 206 - Introducing Blocks and Grand Central Dispatch on iPhone
  • WWDC 2010 Session 211 - Simplifying iPhone App Development with Grand Central Dispatch

I wrote a wrapper class for asynchronous NSURLConnection.


AsyncURLConnection.h

#import <Foundation/Foundation.h>

typedef void (^completeBlock_t)(NSData *data);
typedef void (^errorBlock_t)(NSError *error);

@interface AsyncURLConnection : NSObject
{
    NSMutableData *data_;
    completeBlock_t completeBlock_;
    errorBlock_t errorBlock_;
}

+ (id)request:(NSString *)requestUrl completeBlock:(completeBlock_t)completeBlock errorBlock:(errorBlock_t)errorBlock;
- (id)initWithRequest:(NSString *)requestUrl completeBlock:(completeBlock_t)completeBlock errorBlock:(errorBlock_t)errorBlock;
@end

AsyncURLConnection.m

#import "AsyncURLConnection.h"

@implementation AsyncURLConnection

+ (id)request:(NSString *)requestUrl completeBlock:(completeBlock_t)completeBlock errorBlock:(errorBlock_t)errorBlock
{
    return [[[self alloc] initWithRequest:requestUrl
        completeBlock:completeBlock errorBlock:errorBlock] autorelease];
}

- (id)initWithRequest:(NSString *)requestUrl completeBlock:(completeBlock_t)completeBlock errorBlock:(errorBlock_t)errorBlock
{

    if ((self=[super init])) {
        data_ = [[NSMutableData alloc] init];

        completeBlock_ = [completeBlock copy];
        errorBlock_ = [errorBlock copy];

        NSURL *url = [NSURL URLWithString:requestUrl];
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        [NSURLConnection connectionWithRequest:request delegate:self];
    }

    return self;
}

- (void)dealloc
{
    [data_ release];

    [completeBlock_ release];
    [errorBlock_ release];
    [super dealloc];
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    [data_ setLength:0];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    [data_ appendData:data];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    completeBlock_(data_);
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    errorBlock_(error);
}

@end

How to use AsyncURLConnection class.

/*
 * AsyncURLConnection -request:completeBlock:errorBlock: have to be called
 * from Main Thread because it is required to use asynchronous API with RunLoop.
 */

[AsyncURLConnection request:url completeBlock:^(NSData *data) {

    /* success! */

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        /* process downloaded data in Concurrent Queue */

        dispatch_async(dispatch_get_main_queue(), ^{

            /* update UI on Main Thread */

        });
    });

} errorBlock:^(NSError *error) {

    /* error! */

}];
Kazuki Sakamoto
  • 13,929
  • 2
  • 34
  • 96
  • 1
    this is a great code sample, thanks for sharing. if I wanted to add a `cancel` method to `AsyncURLConnection` that would cancel the `NSURLConnection` within, how would you recommend implementing that? Add an iVar to hold the connection and simply call `cancel` on it? – XJones Apr 16 '11 at 03:19
  • 1
    I changed the class for cancel. [ASyncURLConnection.h](https://gist.github.com/927491) and [AsyncURLConnection.m](https://gist.github.com/927493). The class inherited fron NSURLConnection, thus you can call cancel method of the instance of the class. But, I have not tested yet :-) – Kazuki Sakamoto Apr 19 '11 at 13:02
  • 34
    The lecturer does say threads are evil, but he doesn't say GCD is evil, he says it's the future. At the time of the lecture he says not to use GCD because it's not in the foundation framework for networking yet, but now it is: `[NSURLConnection sendAsynchronousRequest:queue:completionHandler:]` – keegan3d Nov 18 '11 at 05:26
  • 14
    This entire answer seems to be obsolete now - see comment from @keegan3d – whitneyland Nov 19 '11 at 14:39
  • great code i was just trying to understand your code so that i can use it but I have one doubt why you are copying the block,what is the purpose of that? – Anshul Feb 11 '14 at 10:32
  • Usually blocks are created on the stack, you need to call Block_copy(or Objective-C copy method) for copying the block object to the heap from the stack in order to retain it. – Kazuki Sakamoto Feb 12 '14 at 17:03
1

Create a concurrent NSOperation on which you run your asynchronous NSURLConnection.

Nyx0uf
  • 4,609
  • 1
  • 25
  • 26
  • So in other words, the gcd approach is not the right one for this? I thought that the gcd was the way to go to simplify such things. – Jonas Anderson Feb 22 '11 at 21:20
0

Have a look at this code block:

-(void)getDataFromServer
{
    NSDictionary *dicParams = [NSDictionary dictionaryWithObjectsAndKeys:
                           userId,    kUserID,
                           pageIndex,kPageIndex,
                           nil];

    NSString *yourURL = [self addQueryStringToUrlString:[NSString stringWithFormat:@"%@/%@",_PATH_,apiName] withDictionary:dicParams];


    NSString *queue_id = @"_queue_id_";
    dispatch_queue_t queue = dispatch_queue_create([queue_id UTF8String], 0);
    dispatch_queue_t main = dispatch_get_main_queue();

    dispatch_async(queue, ^{

        NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:yourURL] 
                                                    cachePolicy:NSURLRequestReloadIgnoringCacheData 
                                                timeoutInterval:60.0];
        [theRequest setHTTPMethod:@"GET"];
        [theRequest setValue:@"application/json" forHTTPHeaderField:@"Accept"];
        [theRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];

        NSError        *serviceError = nil;
        NSURLResponse  *serviceResponse = nil;
        NSData *dataResponse = [NSURLConnection sendSynchronousRequest:theRequest 
                                                     returningResponse:&serviceResponse 
                                                                 error:&serviceError];

        if(serviceError)
        {
            dispatch_sync(main, ^{

                // Update your UI

                if(serviceError.code == -1012){
                    // Log error
                }else{
                    // Log error
                }
            });
        }

        else
        {
            NSError *jsonError = nil;

            id dataObject = [NSJSONSerialization JSONObjectWithData:dataResponse 
                                                            options:kNilOptions 
                                                              error:&jsonError];
            NSMutableArray *arrResponse = (NSMutableArray *)dataObject;

            dispatch_sync(main, ^{

                // Update your UI
                [yourTableView reloadData];
            });
        }
    });
}

+(NSString*)addQueryStringToUrlString:(NSString *)urlString withDictionary:(NSDictionary *)dictionary
{
    NSMutableString *urlWithQuerystring = [[NSMutableString alloc] initWithString:urlString];

    for (id key in dictionary) {
        NSString *keyString = [key description];
        NSString *valueString = [[dictionary objectForKey:key] description];

        if ([urlWithQuerystring rangeOfString:@"?"].location == NSNotFound) {
            [urlWithQuerystring appendFormat:@"?%@=%@", [self urlEscapeString:keyString], [self urlEscapeString:valueString]];
        } else {
            [urlWithQuerystring appendFormat:@"&%@=%@", [self urlEscapeString:keyString], [self urlEscapeString:valueString]];
        }
    }
    return urlWithQuerystring;
}

+(NSString*)urlEscapeString:(NSString *)unencodedString
{
    CFStringRef originalStringRef = (__bridge_retained CFStringRef)unencodedString;
    NSString *s = (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,originalStringRef, NULL, NULL,kCFStringEncodingUTF8);
    CFRelease(originalStringRef);
    return s;
}
NSPratik
  • 4,714
  • 7
  • 51
  • 81