I was recently helping a friend debug an interesting piece of code to asynchronously download images. Without giving away the application idea, let me explain what we were attempting to do from the 30,000 foot view and then share what we discovered, hopefully saving you time should you ever need to implement similar functionality.
The basic idea was to download thumbnail images in the background, and update rows in a table in realtime, as the images were retrieved. This lends to a really nice visual effect on the iPhone. Picture a table with two columns; the right column displays text and the left column shows an avatar (as png file). For each visible row, the text is immediately shown, and the image on the left starts out with a placeholder image (packaged with the application). As each avatar is downloaded, the applicable row in the table is updated with the new image.
The basic idea was to create a URL request (NSURLRequest) specifying the desired URL. With this in hand, create a URL connection (NSURLConnection) to load the URL request.
1 2 3
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:_url]]; [NSURLConnection connectionWithRequest:request delegate:self]; [connection start];
From the reference documentation, the description for the start method is as follows: “Causes the receiver to begin loading data, if it has not already.” Seems the right thing to do after creating the URL and connection requests.
Using delegate methods of NSURLConnection, you can receive callbacks for things such as when data is received and when a connection has finished loading. This is handy, as our approach was to initiate additional requests for images while waiting for others to download.
The trouble we ran into was that we kept getting time out errors. The specific error was often displayed as “EXC_BAD_ACCESS” in the debug console. What was odd, was that we could account for each URL request, connection and download completing. It was obvious that a connection was being created somewhere on the way, and timing out, question was, what connection?
Here’s where we went wrong…when calling connectionWithRequest: (line #2 above) an initialized URL connection is returned and the data is immediately requested. The call to start must of begun a new connection request, which would eventually timeout as the original request was already underway. When we removed the call to start the application worked like a champ.
This was a surprisingly tricky bug to track down. From reading the documentation one would think calling start would be a reasonable thing to do, in other words, if the receiver hasn’t begun to download data, begin to do so (it’s obviously not quite that simple). It’s not clear to me when one would use this method. If anyone has some insight, I’d love to hear it.