NSURLDownload provides an application the ability to download the contents of a URL directly to disk. It provides an interface similar to NSURLConnection
, adding an additional method for specifying the destination of the file. NSURLDownload can also decode commonly used encoding schemes such as MacBinary, BinHex and gzip. Unlike NSURLConnection, data downloaded using NSURLDownload is not stored in the cache system.
If your application is not restricted to using Foundation classes, the WebKit framework includes WebDownload, a subclass of NSURLDownload that provides a user interface for authentication.
iOS Note: The NSURLDownload class is not available in iOS, because downloading directly to the file system is discouraged. Use the NSURLConnection class instead. See “Using NSURLConnection” for more information.
One usage pattern for NSURLDownload is downloading a file to a predetermined filename on disk. If the application knows the destination of the download, it can set it explicitly using setDestination:allowOverwrite:
. Multiple setDestination:allowOverwrite:
messages to an NSURLDownload instance are ignored.
The download starts immediately upon receiving the initWithRequest:delegate:
message. It can be canceled any time before the delegate receives a downloadDidFinish:
or download:didFailWithError:
message by sending the download a cancel
message.
The example in Listing 1 sets the destination, and thus requires the delegate only implement the download:didFailWithError:
and downloadDidFinish:
methods.
Listing 1 Using NSURLDownload with a predetermined destination file location
- (void)startDownloadingURL:sender |
{ |
// Create the request. |
NSURLRequest *theRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:CONFIG_SOURCE_URL_STRING] |
cachePolicy:NSURLRequestUseProtocolCachePolicy |
timeoutInterval:60.0]; |
// Create the connection with the request and start loading the data. |
NSURLDownload *theDownload = [[NSURLDownload alloc] initWithRequest:theRequest |
delegate:self]; |
if (theDownload) { |
// Set the destination file. |
[theDownload setDestination:CONFIG_SOURCE_PATH allowOverwrite:YES]; |
} else { |
// inform the user that the download failed. |
} |
} |
- (void)download:(NSURLDownload *)download didFailWithError:(NSError *)error |
{ |
// Release the connection. |
[download release]; |
// Inform the user. |
NSLog(@"Download failed! Error - %@ %@", |
[error localizedDescription], |
[[error userInfo] objectForKey:NSErrorFailingURLStringKey]); |
} |
- (void)downloadDidFinish:(NSURLDownload *)download |
{ |
// Release the connection. |
[download release]; |
// Do something with the data. |
NSLog(@"%@",@"downloadDidFinish"); |
} |
Additional methods can be implemented by the delegate to customize the handling of authentication, server redirects and file decoding.
Sometimes the application must derive the destination filename from the downloaded data itself. This requires you to implement the delegate method download:decideDestinationWithSuggestedFilename:
and call setDestination:allowOverwrite:
with the suggested filename. The example in Listing 2 saves the downloaded file to the desktop using the suggested filename.
Listing 2 Using NSURLDownload with a filename derived from the download
- (void)startDownloadingURL:sender |
{ |
// Create the request. |
NSURLRequest *theRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.apple.com/index.html"] |
cachePolicy:NSURLRequestUseProtocolCachePolicy |
timeoutInterval:60.0]; |
// Create the download with the request and start loading the data. |
NSURLDownload *theDownload = [[NSURLDownload alloc] initWithRequest:theRequest delegate:self]; |
if (!theDownload) { |
// Inform the user that the download failed. |
} |
} |
- (void)download:(NSURLDownload *)download decideDestinationWithSuggestedFilename:(NSString *)filename |
{ |
NSString *destinationFilename; |
NSString *homeDirectory=NSHomeDirectory(); |
destinationFilename = [[homeDirectory stringByAppendingPathComponent:@"Desktop"] |
stringByAppendingPathComponent:filename]; |
[download setDestination:destinationFilename allowOverwrite:NO]; |
} |
- (void)download:(NSURLDownload *)download didFailWithError:(NSError *)error |
{ |
// Release the download. |
[download release]; |
// Inform the user. |
NSLog(@"Download failed! Error - %@ %@", |
[error localizedDescription], |
[[error userInfo] objectForKey:NSErrorFailingURLStringKey]); |
} |
- (void)downloadDidFinish:(NSURLDownload *)download |
{ |
// Release the download. |
[download release]; |
// Do something with the data. |
NSLog(@"%@",@"downloadDidFinish"); |
} |
The downloaded file is stored on the user's desktop with the name index.html
, which was derived from the downloaded content. Passing NO
to setDestination:allowOverwrite:
prevents an existing file from being overwritten by the download. Instead a unique filename is created by inserting a sequential number after the filename, for example, index-1.html
.
The delegate is informed when a file is created on disk if it implements the download:didCreateDestination:
method. This method also gives the application the opportunity to determine the finalized filename with which the download is saved.
The example in Listing 3 logs the finalized filename.
Listing 3 Logging the finalized filename using download:didCreateDestination:
-(void)download:(NSURLDownload *)download didCreateDestination:(NSString *)path |
{ |
// path now contains the destination path |
// of the download, taking into account any |
// unique naming caused by -setDestination:allowOverwrite: |
NSLog(@"Final file destination: %@",path); |
} |
This message is sent to the delegate after it has been given an opportunity to respond to the download:shouldDecodeSourceDataOfMIMEType:
and download:decideDestinationWithSuggestedFilename:
messages.
The progress of the download can be determined by implementing the delegate methods download:didReceiveResponse:
and download:didReceiveDataOfLength:
.
The download:didReceiveResponse:
method provides the delegate an opportunity to determine the expected content length from the NSURLResponse. The delegate should reset the progress each time this message is received.
The example implementation in Listing 4 demonstrates using these methods to provide progress feedback to the user.
Listing 4 Displaying the download progress
- (void)setDownloadResponse:(NSURLResponse *)aDownloadResponse |
{ |
[aDownloadResponse retain]; |
// downloadResponse is an instance variable defined elsewhere. |
[downloadResponse release]; |
downloadResponse = aDownloadResponse; |
} |
- (void)download:(NSURLDownload *)download didReceiveResponse:(NSURLResponse *)response |
{ |
// Reset the progress, this might be called multiple times. |
// bytesReceived is an instance variable defined elsewhere. |
bytesReceived = 0; |
// Retain the response to use later. |
[self setDownloadResponse:response]; |
} |
- (void)download:(NSURLDownload *)download didReceiveDataOfLength:(unsigned)length |
{ |
long long expectedLength = [[self downloadResponse] expectedContentLength]; |
bytesReceived = bytesReceived + length; |
if (expectedLength != NSURLResponseUnknownLength) { |
// If the expected content length is |
// available, display percent complete. |
float percentComplete = (bytesReceived/(float)expectedLength)*100.0; |
NSLog(@"Percent complete - %f",percentComplete); |
} else { |
// If the expected content length is |
// unknown, just log the progress. |
NSLog(@"Bytes received - %d",bytesReceived); |
} |
} |
The delegate receives a download:didReceiveResponse:
message before it begins receiving download:didReceiveDataOfLength:
messages.
In some cases, you can resume a download that was canceled or that failed while in progress. To do so, first make sure your original download doesn’t delete its data upon failure by passing NO
to the download’s setDeletesFileUponFailure:
method. If the original download fails, you can obtain its data with the resumeData
method. You can then initialize a new download with the initWithResumeData:delegate:path:
method. When the download resumes, the download’s delegate receives the download:willResumeWithResponse:fromByte:
message.
You can resume a download only if both the protocol of the connection and the MIME type of the file being downloaded support resuming. You can determine whether your file’s MIME type is supported with the canResumeDownloadDecodedWithEncodingMIMEType:
method.
NSURLDownload
provides support for decoding selected file formats: MacBinary, BinHex and gzip. If NSURLDownload
determines that a file is encoded in a supported format, it attempts to send the delegate a download:shouldDecodeSourceDataOfMIMEType:
message. If the delegate implements this method, it should examine the passed MIME type and return YES
if the file should be decoded.
The example in Listing 5 compares the MIME type of the file and allows decoding of MacBinary and BinHex encoded content.
Listing 5 Example implementation of download:shouldDecodeSourceDataOfMIMEType: method.
- (BOOL)download:(NSURLDownload *)download |
shouldDecodeSourceDataOfMIMEType:(NSString *)encodingType; |
{ |
BOOL shouldDecode=NO; |
if ([encodingType isEqual:@"application/macbinary"]) { |
shouldDecode=YES; |
} else if ([encodingType isEqual:@"application/binhex"]) { |
shouldDecode=YES; |
} else if ([encodingType isEqual:@"application/gzip"]) { |
shouldDecode=NO; |
} |
return shouldDecode; |
} |
Last updated: 2010-03-24