1

I am trying to start a second NSURLConnection after starting the first one. My first one works perfectly: the appropriate delegates are all called, and everything executes as planned. However, after the first one finishes, I create a second one, and nothing happens (the delegate is never called). What could be happening? I know I can't reuse the same NSURLConnection, but I reinitialise it before using it again, so it should be a completely new connection.

Here's my code for starting (both) connections. It's the same instance variable, but it's reinitialised. Also note that the second one is not started until the first one has finished running completely.

if (connection) {
    [connection cancel];
}

currentResponse = nil;
error = nil;

connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
if (!connection) {
    NSLog(@"Connection could not be initialized.");
    [self connectionFinished];
} else {
    [connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [connection start];
}
ferson2020
  • 3,015
  • 3
  • 18
  • 26
  • IS the connection created at all? Do you get the "Connection could not be initialized." message being logged? Is the method where this code resides called? – rdelmar Feb 26 '13 at 21:42
  • This code is being called, and the connection is initialised both times. The initialisation failed message is never logged. – ferson2020 Feb 26 '13 at 21:44
  • are you always on the main thread? – Mike M Feb 26 '13 at 21:54
  • No I'm on a different thread (the same thread both times). – ferson2020 Feb 26 '13 at 21:54
  • hmn, I've never seen delegates get called if the request was on a background thread. – Mike M Feb 26 '13 at 21:55
  • Did you implement didFailWithError:? – rdelmar Feb 26 '13 at 21:57
  • I did; that is never getting called. – ferson2020 Feb 26 '13 at 22:00
  • I copied and pasted your code, and it worked repeatedly for me. You've logged the various delegate methods, and none gets called? – rdelmar Feb 26 '13 at 22:05
  • Delegates get called the first time (didReceiveData, didReceiveResponse, willSendRequest, connectionDidFinishLoading), but not the second time. – ferson2020 Feb 26 '13 at 22:08
  • Well, that's a puzzle. After [connection start], try logging connection.delegate to make sure that didn't go away somehow. Seems unlikely, but I'm grasping at straws here. – rdelmar Feb 26 '13 at 22:12
  • After a lot of hunting, I tried something and suspect it has something to do with the run loop being started/stopped. – ferson2020 Feb 26 '13 at 22:18
  • Are you stopping the currentRunLoop? – rdelmar Feb 26 '13 at 22:23
  • You say you aren't on the main thread, but you don't say what sort of thread you are on. (This is like pulling teeth!) My guess is that either you don't have a run loop, or that your NSOperation is ending, or your GCD task is completing, before the second NSConnection gets a chance to do its callback. – Mark Bernstein Feb 26 '13 at 22:54
  • I suspect that using `setDelegateQueue` (if you're using operation queues) will solve your problem, but I have a secondary observation: If you have to run your two network operations consecutively, you can, but it's worth noting that if they can be run concurrently, you'll see a noticeable performance improvement. Clearly if one is dependent upon the next, you wouldn't do that, but if there is no such dependency, it's worth doing the little extra work to allow concurrent network operations. Clearly, this is secondary to your current issue, but it's something to consider as you move forward. – Rob Feb 27 '13 at 15:47
  • @MarkBernstein I am running an NSOperation, so I suspect you're right: my NSOperation is ending before the second NSConnection gets a chance to do its callback. – ferson2020 Feb 27 '13 at 19:33

1 Answers1

2

You haven't shared how you're running it "on a different thread", but the typical problem is if you are using dispatch queues or operation queues, your connection is running asynchronously, itself, and therefore the dispatched operation is completing and getting released and you're losing your connection.

A couple of possible solutions:

  1. You can perform your network operations synchronously in this background operation of yours (this is the only time you should do synchronous network operations). This is the simplest solution, though you haven't explained what you're doing with your NSURLConnection, so this technique may or may not work for you. But if you're just trying to download something from a URL, you can do:

    NSData *data = [NSData dataWithContentsOfURL:url
                                         options:0
                                           error:&error];
    

    This approach doesn't work if you're doing any a little more complicated that requires the NSURLConnectionDataDelegate methods, such challenge-response authentication or if you are streaming using didReceiveData to reduce your app's memory footprint or for performances reasons, etc. But if you're just trying to download data from a remote server (e.g. retrieving an image, an XML/JSON feed, etc.), this is easiest.

  2. In a similar vein (i.e. you don't need the NSURLConnectionDataDelegate methods), but you're you're creating some rich NSURLRequest for your connection, then you can use either sendAsynchronousRequest or sendSynchronousRequest.

  3. If you need the NSURLConnectionDataDelegate calls, you can use the setDelegateQueue (designating a NSOperationQueue) or scheduleInRunLoop (designating a NSRunLoop), and it will automatically dispatch the connection updates to the appropriate queue/runloop. Just make sure to initWithRequest using the startImmediately option of NO, set the delegate queue or runloop, and then start the connection. With this technique, you preserve the full richness of NSURLConnectionDataDelegate if you absolutely need it.

  4. Alternatively, if you're not using operation queues, you could also keep your background operation alive until the connection is done. This approach gives you synchronous behavior in you background operation (keeping your connection alive), while preserving the NSURLConnectionDataDelegate methods. This technique is demonstrated by Apple's XMLPerformance Sample (see the downloadAndParse method of CocoaXMLParser.m and LibXMLParser.m), where they initiate the NSURLConnection and then use the following construct to keep the background operation alive until the NSURLConnectionDataDelegate methods end up setting the done instance variable:

    do {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode 
                                 beforeDate:[NSDate distantFuture]];
    } while (!done);
    

I confess that I find this final approach vaguely dissatisfying and would lean towards the other alternatives, depending upon what flexibility and functionality you need from your NSURLConnection. For us to provide more meaningful counsel, you just need to provide more information about (a) the sort of work you're doing in your NSURLConnectionDataDelegate methods; and (b) which technology you're using to run your code in the background.

For a few more options, also feel free to see GCD and async NSURLConnection.

Community
  • 1
  • 1
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Wow, what a thorough answer! I appreciate the effort you put into that. It turned out I just needed to make sure I didn't finish the NSOperation before I had finished with all my NSConnections. I definitely wanted to run them synchronously, as one depended on the one before, and I needed the ability to handle redirects. Still, this is a very helpful reference for me. – ferson2020 Feb 27 '13 at 21:12