8

How can I get NSURLConnection to call it's delegate methods from a different thread instead of the main thread. I'm trying to mess around with the scheduleInRunLoop:forMode:but doesn't seem to do what I want.

I have to download a large file and it interrupts the main thread so frequently that some rendering that is happening starts getting choppy.

NSURLRequest * request = [NSURLRequest requestWithURL:url];
NSURLConnection * connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
NSRunLoop * loop = [NSRunLoop currentRunLoop];
NSLog(@"loop mode: %@",[loop currentMode]);
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[connection start];

The other thing I don't see much of is "Modes" There are only two modes documented so not much really to test with.

Any ideas?

Thanks

gngrwzrd
  • 5,902
  • 4
  • 43
  • 56

4 Answers4

6

There are several options:

  1. In your implementation of the delegate methods, make use of dispatch_async.
  2. Start the schedule the connection on a background thread.

You can do the latter like this:

// all the setup comes here
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSRunLoop *loop = [NSRunLoop currentRunLoop];
    [connection scheduleInRunLoop:loop forMode:NSRunLoopCommonModes];
    [loop run]; // make sure that you have a running run-loop.
});

If you want a guarantee on which thread you're running, replace the call to dispatch_get_global_queue() appropriately.

danyowdee
  • 4,658
  • 2
  • 20
  • 35
  • 1
    doesn't his keep around the thread the queue runs on? So, I call this once, thread is kept around. I call this again, since GCD decides on what thread the queue runs on then possibly another thread is kept around and so on... So, isn't this possibly keeping around threads for no reason once work is done on threads used be the global queue? Also what do you mean by "If you want a guarantee on which thread you're running, replace the call to dispatch_get_global_queue() appropriately."? How can you target a thread through GCD? – zumzum Jul 08 '14 at 15:54
2

If you want to perform downloads on a separate thread, I'm pretty sure these are the droids you're looking for...

- (void) dispatchRequest{
    self->finished = NO;
    NSMutableURLRequest* request = //Formulate your request
    NSThread* download_thread = [[NSThread alloc] initWithTarget:self selector:@selector(downloadThreadLoop:) object:request];
    [download_thread start];
}
- (void) downloadThreadLoop:(NSMutableURLRequest*) request{

    NSURLConnection* connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];

    while(!self->finished]){
        //This line below is the magic!
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
    //...
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
    //...
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
    //...   
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
    //...   
    self->finished = YES;
}
Brooks
  • 2,082
  • 2
  • 18
  • 26
0

If you truly need to do the download in a new thread, it may be easier to detachNewThreadSelector:toTarget:withObject:, setup (and destroy) an NSAutoreleasePool, and then use one of the synchronous selectors like NSData's dataWithContentsOfURL:. This will not make use of the asynchronous NSURLConnectionDelegate.

Because this call is synchronous, it will not return until the file has been downloaded, which will block the main thread, but because you're in a new thread, it won't. Please note that this is typically discouraged behavior. Is there other code happening in the main thread that can be optimized?

greg
  • 4,843
  • 32
  • 47
0

NSURLConnection is already doing the download off of the main thread asynchronously. If I understand your question, you would like the delegate messages to be sent on a thread other than the main thread? You can't do that as you can't modify the internal implementation of NSURLConnection. I can think of two ways to simulate this.

  1. Create a sublcass of NSURLConnection (e.g. MyURLConnection) that assigns itself as own delegate. Note that this creates an intentional retain cycle so be careful. MyURLConnection should define a new delegate that supports NSURLConnectionDelegate. Let's call this finalDelegate. When MyURLConnection handles it's own delegate messages, forward or dispatch them to finalDelegate on whatever thread you like.

  2. Similar to option #1 but without the subclass. Handle the NSURLConnection delegate methods on the main thread and forward/dispatch them to whatever thread you like.

The main difference is if you want a reusable subclass that behaves this way or it's a one off implementation.

EDIT: added suggestion on how to run code in the background

If you are going to process the response in the background I would either use operations or grand central dispatch. No need to mess around with run loops and creating threads. Check out Apple's Concurrency Programming Guide.

Max MacLeod
  • 26,115
  • 13
  • 104
  • 132
XJones
  • 21,959
  • 10
  • 67
  • 82