Apple Developer Connection
Member Login Log In | Not a Member? Contact ADC

< Previous PageNext Page > Hide TOC

Using NSURLDownload

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.

Note: If your application is not restricted to using Foundation classes, the Web Kit framework includes WebDownload, a subclass of NSURLDownload that provides a user interface for authentication.

Contents:

Downloading to a Predetermined Destination
Downloading a File Using the Suggested Filename
Displaying the Download Progress
Handling Request Changes
Handling Authentication Challenges
Decoding Encoded Files


Downloading to a Predetermined Destination

One usage pattern for NSURLDownload is downloading a file to a predetermined filename on the disk. If the application knows the destination of the download, it can explicitly set it 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 now
        [theDownload setDestination:CONFIG_SOURCE_PATH allowOverwrite:YES];
    } else {
        // inform the user that the download could not be made
    }
}
 
 
- (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.

Downloading a File Using the Suggested Filename

Another common situation is that 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 a users 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 connection with the request
    // and start loading the data
NSURLDownload  *theDownload=[[NSURLDownload alloc] initWithRequest:theRequest delegate:self];
    if (!theDownload) {
        // inform the user that the download could not be made
    }
}
 
- (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 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");
}

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.

Displaying the Download Progress

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 release];
    downloadResponse = aDownloadResponse;
}
 
- (void)download:(NSURLDownload *)download didReceiveResponse:(NSURLResponse *)response
{
    // reset the progress, this might be called multiple times
    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.

Handling Request Changes

It’s not uncommon for a server to redirect a request for one URL to another URL. The NSURLDownload delegate receives a download:willSendRequest:redirectResponse: when this occurs.

If the delegate implements this method, it can examine the new NSURLRequest and the NSURLResponse that caused the redirect and allow the redirected NSURLRequest to be used for the download, create a new NSURLRequest for the download, reject the redirect and return any data received from the NSURLResponse that caused the redirect, or cancel the download entirely.

To allow the redirect to occur, the delegate implementation should return the NSURLRequest passed to the delegate method. The delegate could also create a new NSURLRequest, pointing to a new URL, and return that request.

If the delegate wishes to reject the redirect, but receive any existing data for the connection, it should return nil.

Finally, the delegate can cancel the redirect and the connection by calling [connection cancel].

The delegate also receives this message if the NSURLProtocol subclass that handles the request has changed the NSURLRequest in order to standardize its format. For example, changing a request for “http://www.apple.com” to “http://www.apple.com/“. This is required because the standardized, or canonical, version of the request is used for cache management. In this special case the response passed to the delegate is nil and the delegate should simply return the provided NSURLRequest.

The example in Listing 5 allows canonical changes and denies all server redirects.

Listing 5  Example download:willSendRequest:redirectResponse: implementation.

-(NSURLRequest *)download:(NSURLDownload *)download
            willSendRequest:(NSURLRequest *)request
           redirectResponse:(NSURLResponse *)redirectResponse
{
    NSURLRequest *newRequest=request;
    if (redirectResponse) {
        newRequest=nil;
    }
    return newRequest;
}

If the delegate doesn’t implement download:willSendRequest:redirectResponse:, the default behavior is to allow all canonical changes and server redirects.

Handling Authentication Challenges

If a request requires authentication and there are no valid credentials available, either as part of the requested URL or in the shared NSURLCredentialStorage, the NSURLDownload delegate receives a download:didReceiveAuthenticationChallenge: message. In order for the download to continue, the delegate must provide credentials, attempt to continue without credentials, or cancel the authentication request.

The NSURLAuthenticationChallenge (the challenge) passed to the delegate contains information about what triggered the authentication challenge, the number of attempts that have been made for the challenge, any attempted credentials, the NSURLProtocolSpace that requires the credentials, and the sender of the challenge.

If the authentication challenge has tried to authenticate and failed, the attempted credentials are returned by calling [challenge proposedCredential]. The delegate can then use the previously attempted credential to populate a dialog and prompt the user.

The number of attempts at authentication for the challenge is returned by calling [challenge previousFailureCount]. The delegate can pass this information along to the end user, to determine if the credentials supplied previously are failing, or to limit the maximum number of authentication attempts.

To attempt to authenticate, the application should create an NSURLCredential object with the user name, password and the type of persistence to use for the credentials, and then send the [challenge sender] a useCredential:forAuthenticationChallenge: message.

If the delegate chooses not to provide a credential for the authentication challenge it can attempt to continue without one by sending [challenge sender] a continueWithoutCredentialsForAuthenticationChallenge: message. Depending on the protocol implementation, this may return alternate URL contents that don’t require authentication or cause the connection to fail, receiving a connectionDidFailWithError: message.

The delegate may choose to cancel the authentication challenge by sending cancelAuthenticationChallenge: to [challenge sender]. The delegate then receives a connection:didCancelAuthenticationChallenge: message, providing the opportunity to give the end user feedback.

The example in Listing 6 attempts to authenticate the challenge by creating an NSURLCredential instance using a user name and password supplied by the application’s preferences. If the authentication has failed previously, it cancels the authentication challenge and informs the user.

Listing 6  Example download:didReceiveAuthenticationChallenge: implementation.

-(void)download:(NSURLDownload *)download
        didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
    if ([challenge previousFailureCount] == 0) {
        NSURLCredential *newCredential;
        newCredential=[NSURLCredential credentialWithUser:[self preferencesName]
                                                 password:[self preferencesPassword]
                                              persistence:NSURLCredentialPersistenceNone];
        [[challenge sender] useCredential:newCredential
               forAuthenticationChallenge:challenge];
    } else {
        [[challenge sender] cancelAuthenticationChallenge:challenge];
        // inform the user that the user name and password
        // in the preferences are incorrect
        [self showPreferencesCredentialsAreIncorrectPanel:self];
    }
}

Decoding Encoded Files

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 7 compares the MIME type of the file and allows decoding of MacBinary and BinHex encoded content.

Listing 7  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;
}


< Previous PageNext Page > Hide TOC


Last updated: 2007-07-10




Did this document help you?
Yes: Tell us what works for you.

It’s good, but: Report typos, inaccuracies, and so forth.

It wasn’t helpful: Tell us what would have helped.
Get information on Apple products.
Visit the Apple Store online or at retail locations.
1-800-MY-APPLE

Copyright © 2007 Apple Inc.
All rights reserved. | Terms of use | Privacy Notice