GCD and async NSURLConnection

14,013

Solution 1

So really the issue isn't the lifetime of the thread on which your block runs, it's the fact that this particular thread is not going to have a runloop configured and running to receive any of the events coming back from the connection.

So how do you solve this? There are different options to think about. I can list a few, and I'm sure others will list more.

1 - You could use a synchronous connection here. One disadvantage is that you won't get callbacks for authentication, redirection, caching, etc. (All the normal disadvantages of synchronous connections.) Plus each connection will of course block a thread for some period of time, so if you're doing a lot of these then you could potentially have a few threads blocked at once, which is expensive.

2 - If your connection is simple and you are using iOS5 then you can use this method:

+ (void)sendAsynchronousRequest:(NSURLRequest *)request
                          queue:(NSOperationQueue*) queue
              completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))

This will start an asynchronous connection and then allow you to specify a completion handler (for success or failure) and a NSOperationQueue on which you want that block to be scheduled.

Again, you have the disadvantages of not getting the callbacks you might need for authentication, caching, etc. But at least you don't have threads hanging around blocked by connections that are in flight.

3 - Another option for iOS5 is to set the queue for all delegate callbacks:

- (void)setDelegateQueue:(NSOperationQueue*) queue NS_AVAILABLE(10_7, 5_0);

If you use this, then all of the delegate methods will be executed in the context of whatever NSOperationQueue you specify. So this is similar to option #2, expect that you get all of the delegate methods now to handle authentication, redirection, etc.

4 - You could set up your own thread that you control specifically for managing these connections. And in setting up that thread, you configure a runloop appropriately. This would work fine in iOS4 and 5 and obviously gives you all of the delegate callbacks that you want to handle

5 - You might think about what parts of your asynchronous connection handling are really interfering with your UI. Typically kicking off the connection or receiving delegate callbacks are not that expensive. The expensive (or indeterminate) cost is often in the processing of the data that you collect at the end. The question to ask here is are you really saving time by scheduling a block on some queue just to start an asynchronous connection that will go off immediately and do its thing on another thread anyway?

So you could just start the connection from the main thread, and receive all of the delegate callbacks on the main thread, and then in your implementation of those delegate methods fire off whatever expensive work you need to do on some other queue or thread.

So something like this:

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {      
        // go ahead and receive this message on the main thread
        // but then turn around and fire off a block to do the real expensive work

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

         // Parse the data we've been collecting

        });

    }

Again, this is not comprehensive. There are many ways to handle this, depending on your specific needs here. But I hope these thoughts help.

Solution 2

Just as an answer to why your thread was disppearing (and for future reference) the NSURLConnection needs a runloop. If you had added

[[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]];

You'd see that the connection runs properly and the thread doesn't disappear untill the connection was completed.

Solution 3

First off, your block and every variable you use within it will get copied to GCD, so the code will not be executed on your thread but on the global queue.

If you want to get your data back on the main thread, you can nest an async call after your data has been fetched:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"www.stackoverflow.com"]];
    NSURLResponse *response;
    NSError *error;
    NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
    if (error) {
        // handle error
        return;
    }
    dispatch_async(dispatch_get_main_queue(), ^{
        // do something with the data
    });
});

But why not use NSURLConnection's built in asynchronous support? You need an NSOperationQueue, but if you are doing alot of network fetches it is the way to go anyway:

NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"www.stackoverflow.com"]];
[NSURLConnection sendAsynchronousRequest:request
                                   queue:self.queue // created at class init
                       completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
                           // do something with data or handle error
                       }];

Personally, I use a library like AFNetworking or ASIHTTPRequest to make networking even easier, which both support blocks (the former utilizes GCD and is a bit more modern).

Share:
14,013

Related videos on Youtube

bandejapaisa
Author by

bandejapaisa

Freelance iOS Developer in London, UK. [LinkedIn Profile][1] Owner of [DevedUp Ltd][2]

Updated on June 04, 2022

Comments

  • bandejapaisa
    bandejapaisa about 2 years

    I know that if I create an NSURLConnection (standard async one), it will call back on the same thread. Currently this is on my main thread. (work fine too).

    But i'm now using the same code for something else, and I need to keep my UI snappy....

    If i do

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    
        /* and inside here, at some NSURLConnection is created */
    
    
    });
    

    .. is it possible that my NSURLConnection is created but my thread disappears before the url connection has returned?

    I'm new to GCD. How would one keep the thread alive until my url connection returned, or is there a better way I could be doing this?

  • bandejapaisa
    bandejapaisa over 12 years
    Great comprehensive response. Point 5 is my problem - I'm optimising too early (isn't this a programming sin?). And you also explained that because I don't have a run loop configured on this thread. Thanks for pointing out the alternatives too.
  • bandejapaisa
    bandejapaisa over 12 years
    Thanks for the tips. My dispatch_async wasn't at the point where I create the connection ( agreed this wasn't clear in my question ), the dispatch was quite a bit earlier down the call stack before the url connection was created. I think what I really need is a relook at my design - as I'm using a RemoteImage class in different contexts and I need to make it asynchronous internally.
  • FrostyL
    FrostyL over 10 years
    I really like this approach. Great answer