Async networking + threading problem
- by randallmeadows
I kick off a network request, assuming no login credentials are required to talk to the destination server. If they are required, then I get an authentication challenge, at which point I display a view requesting said credentials from the user. When they are supplied, I restart the network request, using those credentials.
That's all fine and dandy, as long as I only do one request at a time. But I'm not, typically.
When both requests are kicked off, I get the first challenge, and present the prompt (using -presentModalViewController:). Then the 2nd challenge comes in. And I crash when it tries to display the 2nd prompt.
I have the bulk of this wrapped in an @synchronized() block, but this has no effect because these delegate methods are all being called on the same (main) thread. The docs say the delegate methods are called on the same thread in which the connection was started. OK, no problem; I'll just write a method that I run on a background thread using -performSelectorInBackground:
NSURLConnection *connection = [[NSURLConnection alloc]
initWithRequest:request
delegate:self
startImmediately:NO];
[connections addObject:connection];
[self performSelectorInBackground:@selector(startConnection:)
withObject:connection];
[connection release];
- (void)startConnection:(NSURLConnection *)connection {
NSAutoreleasePool *pool = [NSAutoreleasePool new];
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[connection start];
[pool drain];
}
which should put every network request, and its callbacks, on its own thread, and then my @synchronized() blocks will take effect.
The docs for -initWithRequest:... state "Messages to the delegate will be sent on the thread that calls this method. By default, for the connection to work correctly the calling thread’s run loop must be operating in the default run loop mode." Ok, I'm doing that.
They also state "If you pass NO [for startImmediately], you must schedule the connection in a run loop before starting it." OK, I'm doing that, too.
Furthermore, the docs for NSRunLoop state "Each NSThread object, including the application’s main thread, has an NSRunLoop object automatically created for it as needed. If you need to access the current thread’s run loop, you do so with the class method currentRunLoop." I'm assuming this applies to the background thread created by the call -performSelectorInBackground... (which does appear to be the case, when I execute 'po [NSClassFromString(@"NSRunLoop") currentRunLoop]' in the -startConnection: method).
The -startConnection: method is indeed being called. But after kicking off the connection, I now never get any callbacks on it. None of the -connectionDid… delegate methods. (I even tried explicitly starting the thread's run loop, but that made no difference; I've used threads like this before, and I've never had to start the run loop manually before--but I'm now grasping at straws...)
I think I've come up with a workaround such that I only handle one request at a time, but it's kludgy and I'd like to do this the Right Way. But, what am I missing here?
Thanks!
randy