Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSURLConnection started in another thread. Delegate methods not called

I start a NSURLConnection in another thread:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),
        ^{
            NSURLConnection *connection = [NSURLConnection connectionWithRequest:[request preparedURLRequest] delegate:self];
            [connection start];
         });

But my delegate method is not called:

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData*)data;

When run on the main thread everything is fine. How can I run connection on another thread and get the delegate methods called at the same thread too?

like image 775
Roo Avatar asked Aug 21 '15 18:08

Roo


3 Answers

GCD creates, destroys, reuses threads implicitly and there is a chance that the thread you call start from will stop existing immediately afterwards. This may result in the delegate not receiving any callbacks.

If you would like to receive callback in background thread, you can use setDelegateQueue or sendAsynchronousRequest:queue:completionHandler: method:

NSURLConnection* connection = [[NSURLConnection alloc] initWithRequest:request
                                                          delegate:self
                                                  startImmediately:NO];
[connection setDelegateQueue:[[NSOperationQueue alloc] init]];
[connection start];

The easiest way to start NSURLConnection in the background thread via GCD is:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
               ^{
                   NSURLResponse* response = nil;
                   NSError* error = nil;
                   [NSURLConnection sendSynchronousRequest:request] returningResponse:&response error:&error];
                   NSLog(@"%@", response);
               });
like image 104
sgl0v Avatar answered Dec 01 '22 01:12

sgl0v


Yes, this is well known behavior of NSURLConnection because it needs a run loop to process the delegate events. The most common solution is (a) instantiate it with initWithRequest:delegate:startImmediately: where startImmediately is FALSE; (b) manually scheduleInRunLoop:forMode: to schedule it in the main run loop; and then (c) start the connection.

But, as you have it here, there's no point in dispatching this to a background queue, as it's already asynchronous so you should just initiate this from the main queue and none of the above is necessary. You use the above pattern in special cases (e.g. you were using NSOperation subclass to manage your requests), but generally it's not needed.

Also, FYI, effective iOS9, NSURLConnection is deprecated, so you should be using NSURLSession, anyway. And NSURLSession doesn’t suffer this limitation.

like image 29
Rob Avatar answered Dec 01 '22 00:12

Rob


I had a similar issue. What I'm doing now is running NSURLConnection request in the main thread - it is running asynchronously so it won't slow down your application. In connectionDidFinishLoading, I run the following code to process the results of my calls. I perform the check because I have NSURLConnection call which may trigger other network calls. Since they are already running on a background thread I don't want to start a new one.

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
   if ([NSThread isMainThread]) {
      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
         //Background Thread
         [self processFinishLoading:connection];
      });
   }
   else {
      [self processFinishLoading:connection];
   }
}
like image 26
Gjchoza Avatar answered Dec 01 '22 01:12

Gjchoza