iOS 5 introduces sendAsynchronousRequest:queue:completionHandler:,
a great new API that makes it easy to dispatch a NSURLRequest
and safely
receive a callback when it finishes.
AEURLConnection
is a simple reimplementation of the API for use on iOS 4.
Used properly, it is also guaranteed to be safe against The Deallocation Problem,
a thorny threading issue that affects most other networking libraries.
[AEURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
// Handle the response, or error.
}];
Read up on it here.
If you are making asynchronous network requests from a UIViewController
,
your app almost certainly will crash under certain circumstances. (Of
course, you shouldn't be calling the network from UIViewController
if
you're implementing MVC properly, but that's another story!) Here's a
short summary:
- UIViewController must be deallocated on the main thread.
- Depending on how you are issuing asynchronous network requests, it is
likely that your
UIViewController
is being retained by a background thread.- If you're using
-performSelectorInBackground:withObject:
and then calling+sendSynchronousRequest:returningResponse:error:
, you are spawning a background thread that retainsUIViewController
since it is the target of the invocation. - If you're using
NSOperation
in any way—e.g. ASIHTTPRequest orAFHTTPRequestOperation
from AFNetworking—you're almost certainly retaining yourUIViewController
, unless you have total separation between the controller and a model layer that never lets the controller see a secondary thread. If you set acompletionBlock
on an operation that references the view controller, or reference the view controller from an AFNetworking success/failure block, you're retaining the controller on a background thread.
- If you're using
- If the background thread is the last object to release your
UIViewController
, your app will crash.- This can happen if the user pops a view controller (by tapping the back button) before a running operation completes.
- To see it in action, open your app on a slow network connection. Open a view that loads data from the network, then immediately press back. If you're vulnerable, your app will crash.
It's a nasty problem that's extremely difficult to work around. If you're
using an NSOperation
, the only way to prevent it is to:
-
Never reference
self
or any ivars in the completion block -
Create a
__block id blockSelf
variable to refer toself
, like so:block id blockSelf = [self retain]; [myOperation setCompletionBlock:^{ dispatch_async(dispatch_get_main_queue(), ^{ [blockSelf operationFinishedWithData:[myOperation data]]; [blockSelf release]; } // Prevent retain cycle since completionBlock references // myOperation [myOperation setCompletionBlock:nil]; }];
Or, the simpler option: don't use NSOperation
at all. Instead use
AEURLConnection
.
First, it allows you to specify a queue that you want to receive the response
on, instead of giving it to you on a random background thread. Most of the
time you'll want to specify [NSOperationQueue mainQueue]
, which will execute
the completion handler on the main thread.
Second, the completionHandler
block is guaranteed to be released on that
same queue. This means you can capture UIViewControllers
willy-nilly without
worrying; the completionHandler
, and thus all the view controllers it
captures, will safely be released on the main thread.
You might need to use an NSOperation
if:
- You need to limit the number of requests being issued simultaneously.
- You need the ability to cancel a request, or get the request progress.
- You need to download large files.
NSURLConnectionDownloadDelegate
provides a better solution for this, but it's iOS 5 only.
I'm working on a solution for number 2 that allows you to pass an
options
dictionary with blocks for progress updates, and returning an object
to the caller that can be canceled.