One thing I had to do very recently was to download a relatively short block of data from my server. There are some nice Cocoa shortcuts for such a thing, like:
NSError* error = nil;
NSData* data = [NSData dataWithContentsOfURL:theURL options: NSUncachedRead error:&error];
which accesses that URL from the server, presents it to you as NSData and/or gives you an error object. The problem, however, is that it’s synchronous; if your server is down, it may take some time to time-out, and all that time your UI is inactive.
The initial impulse to get around this problem would be to simply spin those two lines off into a secondary thread. It’s easy to underestimate the subtleties of that, however, and there are good alternatives available. Here’s a simplified version of what I eventually elected to use. First the .h file:
@interface AsynchDownload : NSObject {
NSURLConnection* conn;
NSMutableData* data;
id delegate;
}
- (id)initWithURL:(NSURL*)theURL delegate:(id)theDelegate;
- (void)cancel;
@end
and here the .m file:
@implementation AsynchDownload
- (id)initWithURL:(NSURL*)theURL delegate:(id)theDelegate {
if ((self = [super init])) {
delegate = theDelegate;
data = nil;
conn = [[NSURLConnection alloc]
initWithRequest:[NSURLRequest requestWithURL:theURL]
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:60.0]
delegate:self];
}
return self;
}
- (void)cancel {
[conn cancel];
}
- (void)dealloc {
[conn release];
[data release];
[super dealloc];
}
- (void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error {
[delegate performSelectorOnMainThread:@selector(failure:)
withObject:[error localizedDescription] waitUntilDone:NO];
}
- (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response {
[data release];
data = nil;
}
- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)theData {
if (!data) {
data = [[NSMutableData alloc] init];
}
[data appendData:theData];
}
- (void)connectionDidFinishLoading:(NSURLConnection*)connection {
[delegate performSelectorOnMainThread:@selector(success:)
withObject:data waitUntilDone:NO];
}
@end
and, finally, here’s how to call it:
AsynchDownload* download = [[AsynchDownload alloc] initWithURL:theURL delegate:self];
… and in the same class, two methods:
- (void)failure:(NSString*)failure {
[download release];
//.. complain and show the failure string
}
- (void)success:(NSData*)data {
[download release];
// do something with data
}
… and finally, you might call
[download cancel];
somewhere inside, say, a cancel button’s action.
This takes care of nearly all common cases. The only non-obvious part is the connection:didReceiveResponse: method; according to the docs, you need to discard already-received data there, as it may be sent multiple times before you get the final data. In this method, you could also ask the NSURLResponse for the data size, to set a download progress indicator.
Finally, note the performSelectorOnMainThread: messages which pass the results back to your own code; they neatly insulate you from threading effects.
Leave a Comment