Running an Asynchronous NSURLConnection on a POSIX Thread

Recently I worked in a code base where all of the network communication runs on background threads… surprise surprise. However, the analytics systems ( that’s right, there were a couple ) run through a generic message queue that runs on a POSIX worker thread for maximum portability. The network connection code is platform specific, I assume because doing a web transaction using the iOS SDK vs doing it in cURL is much more straight forward. Initially, the connection was made using the synchronous API call for the NSURLConnection object. Almost every blog I’ve found online that talks about NSURLConnection says to avoid using the synchronous connection call because it blocks whatever thread it’s on and usually that’s the main thread. The exception is if your app is multi-threaded, in which case things just got a lot more complicated. Well this app was multithreaded and the threading solution had been unit tested over and over again, so there was high confidence that blocking on our background threads was fine.

For months, we’d been receiving crash reports from beta testers showing a crash when releasing some portion of a cached connection on the NSURLConnectionLoader thread – which is an Apple controlled thread. But we’ve rarely been able to reproduce the crash, so figuring out the exact cause and regression testing has been quite difficult. One thing I wanted to try was to make the synchronous connections on our POSIX threads use the asynchronous API. I know my way around the iOS SDK but I’m generally unfamiliar with the inner workings of a lot of the Core Foundation classed. Every NSURLConnection I’ve ever kicked off has been on the main thread ( of course they were asynchronous but the SDK handles that ), so things just worked. So when I kicked off the asynchronous connection on a background thread, I expected the same result… alas, this was not the case.

The delegates that get called on an asynchronous connection need one more mechanism to actually work. The main thread of an iOS/OSX app actually provides this for you without you having to do anything. So it’s natural to think that you may not have to do anything for other threads. This mechanism is called a Run Loop. Game developers are likely more familiar with explicit run loops than app developers but run loops provide a mechanism for your app to update itself, over, and over, and over, and over…

Specifically in iOS/OSX, there is a class called NSRunLoop. The underlying Core Foundation functions in the CFRunLoop family. These run loops can be set on timers, which is the run loop most developers are familiar with, but they can also be set to wait on other inputs. In this case, we’re waiting on a socket to get network data. All of this is concealed behind the API so I urge you to have a read through the threading/run loop docs for iOS.

One other difference in this case was that we were actually running our own run loop in the thread function. The threading setup I was working in doesn’t assume you want to have a run loop on a secondary thread and the run loop for this particular function was portable C++, so I had implement a solution assuming I’m already in a run loop on the iOS layer. Which leads me to the point of this post…

Since the class that actually does the network communication already had OS-specific implementations, I decided to put the iOS run loop there. The other reason why this worked is because the function that makes the connection was already expecting a synchronous connection, so it was meant to block on the connection. Here’s what it looks like:

// someURL was declared and configured above
NSMutableURLRequest* request = [ NSURLRequest requestWithURL: someURL ];

// configure request

// make connection
[ NSURLConnection connectionWithRequest: request ];

// block on an asynchronous NSURLRequest
// connectionComplete is set in the connection delegates when the connection finishes or fails
NSRunLoop *loop = [NSRunLoop currentRunLoop];
while ( ( !connectionComplete ) &&
        ( [loop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture] ] ) )

The while loops says “if the connection is not complete, block on the run loop until something triggers the loop to continue.” NSDefaultRunLoopMode means to listen for more than just sockets but for our needs, it works just fine. The before date being set to the distant future means this loop will block forever until something comes in to make it continue, so make sure you set an appropriate timeout for the NSURLRequest.

If you do have access to the Core Foundation libraries in the code where you setup your own run loop, I believe that calling CFRunLoopInMode passing in 0 for the seconds parameter at the top of your run loop will have the same affect, without blocking. Passing in zero tells the library to do a sweep of the input sources. If an input source has something, you get it, if not, it just continues.

So that’s all I got. Please let me know if I interpreted any of this improperly or am incorrect on any of my assumptions. Thanks!

Other Sources: Core Objective-C in 24 Hours

Wednesday, May 21st, 2014 Blog