iOS Development Journal

Tips and Tricks Learned the Hard Way

Running URL Requests in the Background

Getting data from the web is a pretty common operation for apps these days. Part of the marvel of the iPhone is that you’re always connected. Now, often we make network requests and don’t worry much about them until they’re done. What you might not know is where the code to handle the request is running. And by default that place often ends up being the main thread.

AFNetworking takes care of this issue for you. If you’re curious how it works, or can’t use AFNetworking then read on.

So if you’d like to learn more about how to handle NSURLConnections in the background, read more. If not, just go and use AFNetworking.

There’s 3 basic ways to make requests to get data from a URL. All of them involve the class NSURLConnection.

  1. sendSynchronousRequest:returningResponse:error: – This will block until the request is complete. Which means if you run it on the main thread, the main thread will be blocked. So basically you probably don’t want to use this method.

  2. sendAsynchronousRequest:queue:completionHandler: – This blocks until the request is complete and runs the block completionHandler on queue when it’s done. So this seems useful, but might cause performance problems when you aren’t expecting any.

  3. The traditional method, initializing an NSURLConnection, and telling it to start. By default this runs on the thread where it is started, so usually the main thread.

So we’ll be focusing on the traditional method, since it is the only one that really does what we want.

If you’re already just calling start you might wonder why it’s not causing big issues. Well, if you’ve programmed the delegate callbacks well, then you’re only blocking the main thread very briefly. iOS is already pretty smart about how often it calls the delegate. Problems might come up if you start doing a lot of work from the callback methods, often times in the completion callback. You might notice problems like the UI being unresponsive, or animation jitter. Thankfully, we can fix this.

Telling NSURLConnection Where to Work

There are three ways to tell NSURLConnection where to run its callbacks.

Calling start from Another Thread

This sounds easy enough, that’s why we have dispatch_async right? Well, sadly, no that doesn’t work. You see GCD queues are not the same as threads1, and when you call dispatch_async for a particular queue all it guarantees is what order it will start relative to other blocks in the queue. GCD will create and destroy threads frequently and there’s a good chance2 that the thread you call start from will stop existing immediately afterwards. This results in the delegate not receiving any callbacks.

Instead you need to run start from a specific thread. Doing this is very similar to the next method, which provides more flexibility anyhow, and only two more lines of code.

scheduleInRunLoop:forMode:

This method which works for all versions of iOS is using scheduleInRunLoop:forMode:, which allows you to specify where delegate callbacks will occur. It does this by specifying an NSRunLoop.

NSRunLoops are a representation of a thread. To get an instance of NSRunLoop you call the class method [NSRunLoop currentRunLoop] which returns a run loop for the current thread.

For this to work you still need to have code run in a specific thread, and then call [NSRunLoop currentRunLoop] from within that, and pass it to scheduleInRunLoop:forMode:. Because of compatibility, this is how AFNetworking manages it’s callbacks asynchronously. Here’s a bit of the code from AFNetworking:

AFURLConnectionOperation.mSource
1
2
3
4
5
6
7
8
9
10
11
self.connection = [[NSURLConnection alloc] initWithRequest:self.request
                                                  delegate:self
                                          startImmediately:NO];

NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
for (NSString *runLoopMode in self.runLoopModes) {
    [self.connection scheduleInRunLoop:runLoop forMode:runLoopMode];
    [self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode];
}

[self.connection start];

If you look a few lines up from this snippet, you see that calling start on an AFURLConnectionOperation runs this bit of code on a separate thread.

You might ask why even call scheduleInRunLoop:forMode: and not just call start. Well if you wanted to configure an NSURLConnection in one place, and then call start from somewhere else, even on a different thread, this would ensure that it runs on the network thread. So by setting the run loop in advance, you can be flexible about when and where you start the connection.

There’s a dirty shortcut allowing you to do this when called within a dispatch_async block, but it’s not a very clean solution, and I don’t recommend it.

setDelegateQueue:

The easiest way to manage where your callbacks are run on iOS 5.0 and up is setDelegateQueue:. You simply create an NSOperationQueue and assign it as the delegateQueue for the NSURLConnection. If I have a connection manager class, this takes two bits of code typically:

Creating an NSOperationQueue
1
2
3
4
5
- (id)init {
    ...
    self.queue = [[NSOperationQueue alloc] init];
    ...
}
An example of assigning an NSOperationQueue to an NSURLConnection
1
2
3
4
5
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:aURLRequest
                                                              delegate:self
                                                      startImmediately:NO];
[connection setDelegateQueue:self.queue];
[connection start];

This removes the need to manage a thread or run loop, and you can focus on the important parts of the code.

Why Would I Use This Instead of AFNetworking?

Well, not very often. The big reason for writing your own connection delegate is usually to have a single place where response handling code exists, and not where the call is. You can do this with AFNetworking, but for simple tasks it might make more sense to handle it yourself. The basic NSURLConnection delegate code is actually quite simple, so might make more sense than adding another framework.

You also might need to support iOS 4.3, and don’t want to use an old release of AFNetworking.

Finally, you might be constrained by the MIT license. Or more likely, your lawyers don’t want you using it.

Update 9/15/2013: Fixed references to sendAsynchronousRequest:queue:completionHandler: since it is actually a poor method to use.


  1. Of course the function dispatch_get_main_queue() does ensure things run on the main thread, but that’s an exception, not a rule.

  2. In my experience a “good chance” is actually “always”. Try this and you’ll be left wondering why your callbacks never get called.