Asynchronous request to the server from background thread

19,340

Solution 1

Yes, the thread is exiting. You can see this by adding:

-(void)threadDone:(NSNotification*)arg
{
    NSLog(@"Thread exiting");
}

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(threadDone:)
                                             name:NSThreadWillExitNotification
                                           object:nil];

You can keep the thread from exiting with:

-(void) downloadImage
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    [self downloadImage:urlString];

    CFRunLoopRun(); // Avoid thread exiting
    [pool release];
}

However, this means the thread will never exit. So you need to stop it when you're done.

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    CFRunLoopStop(CFRunLoopGetCurrent());
}

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

Learn more about Run Loops in the Threading Guide and RunLoop Reference.

Solution 2

You can start the connection on a background thread but you have to ensure the delegate methods are called on main thread. This cannot be done with

[[NSURLConnection alloc] initWithRequest:urlRequest 
                                delegate:self];

since it starts immediately.

Do this to configure the delegate queue and it works even on secondary threads:

NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:urlRequest 
                                                              delegate:self 
                                                      startImmediately:NO];
[connection setDelegateQueue:[NSOperationQueue mainQueue]];
[connection start];
Share:
19,340
Dmytro
Author by

Dmytro

iPhone/C++ engineer

Updated on June 07, 2022

Comments

  • Dmytro
    Dmytro almost 2 years

    I've got the problem when I tried to do asynchronous requests to server from background thread. I've never got results of those requests. Simple example which shows the problem:

    @protocol AsyncImgRequestDelegate
    -(void) imageDownloadDidFinish:(UIImage*) img;
    @end
    
    
    @interface AsyncImgRequest : NSObject
    {
     NSMutableData* receivedData;
     id<AsyncImgRequestDelegate> delegate;
    }
    
    @property (nonatomic,retain) id<AsyncImgRequestDelegate> delegate;
    
    -(void) downloadImage:(NSString*) url ;
    
    @end
    
    
    
    @implementation AsyncImgRequest
    -(void) downloadImage:(NSString*) url 
    {  
     NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:url]
                 cachePolicy:NSURLRequestUseProtocolCachePolicy
                timeoutInterval:20.0];
     NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
     if (theConnection) {
      receivedData=[[NSMutableData data] retain];
     } else {
     }  
    
    }
    
    - (void)connectionDidFinishLoading:(NSURLConnection *)connection
    {
      [delegate imageDownloadDidFinish:[UIImage imageWithData:receivedData]];
      [connection release];
      [receivedData release];
    }
    @end
    

    Then I call this from main thread

    asyncImgRequest = [[AsyncImgRequest alloc] init];
    asyncImgRequest.delegate = self; 
    [self performSelectorInBackground:@selector(downloadImage) withObject:nil];
    

    method downloadImage is listed below:

    -(void) downloadImage
    {
     NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
     [asyncImgRequest downloadImage:@"http://photography.nationalgeographic.com/staticfiles/NGS/Shared/StaticFiles/Photography/Images/POD/l/leopard-namibia-sw.jpg"];
     [pool release];
    }
    

    The problem is that method imageDownloadDidFinish is never called. Moreover none of methods

    - (void)connectionDidFinishLoading:(NSURLConnection *)connection
    - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
    - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse*)response
    

    are called. However if I replace

     [self performSelectorInBackground:@selector(downloadImage) withObject:nil]; 
    

    by

     [self performSelector:@selector(downloadImage) withObject:nil]; 
    

    everything is working correct. I assume that the background thread dies before async request is finished it job and this causes the problem but I'm not sure. Am I right with this assumptions? Is there any way to avoid this problem?

    I know I can use sync request to avoid this problem but it's just simple example, real situation is more complex.

    Thanks in advance.

  • James Wald
    James Wald over 14 years
    After researching for many hours (8+) on how to perform an asynchronous NSURLConnection within an NSInvocationOperation, none of the solutions were as elegant as CFRunLoopRun() and CFRunLoopStop(). This is MUCH better than messing around with "isExecuting" instance variables using the KVO protocol. One thing to note is that I used @selector(performSelectorOnMainThread:withObject:waitUntilDo‌​ne:modes:) passing [NSArray arrayWithObject:NSDefaultRunLoopMode] as the modes. Main thread to satisfy UIKit requirements, NSDefaultRunLoopMode to keep the UI interaction smooth. Thanks a lot!
  • alokoko
    alokoko about 12 years
    For projects compiled with ARC, NSAutoReleasePool is not available, so the code would look like @autoreleasepool { [self downloadImage:urlString]; CFRunLoopRun(); }
  • iOS Monster
    iOS Monster over 11 years
    It is just great, Thank you!
  • Khushbu Shah
    Khushbu Shah about 11 years
    Can we stop this background thread from main thread if I want to stop downloading?
  • Harshal Chaudhari
    Harshal Chaudhari almost 8 years
    As per the Apple Threading Programming Guide, "if you use a thread to perform some long-running and predetermined task, you can probably avoid starting the run loop."