NSURLConnection provides the most flexible method of downloading the contents of a URL. It provides a simple interface for creating and cancelling a connection, and supports a collection of delegate methods that provide feedback and control of many aspects of the connection. These classes fall into five categories: URL loading, cache management, authentication and credentials, cookie storage, and protocol support.
In order to download the contents of a URL, an application needs to provide a delegate object that, at a minimum, implements the following delegate methods: connection:didReceiveResponse:, connection:didReceiveData:, connection:didFailWithError: and connectionDidFinishLoading:.
The example in Listing 1 initiates a connection for a URL. It begins by creating an NSURLRequest instance for the URL, specifying the cache access policy and timeout interval for the connection. It then creates an NSURLConnection instance using the request and specifying the delegate. If NSURLConnection can’t create a connection for the request, initWithRequest:delegate: returns nil. If the connection is successful, an instance of NSMutableData is created to store the data that will be provided to the delegate incrementally.
Listing 1 Creating a connection using NSURLConnection.
// create the request |
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.apple.com/"] |
cachePolicy:NSURLRequestUseProtocolCachePolicy |
timeoutInterval:60.0]; |
// create the connection with the request |
// and start loading the data |
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self]; |
if (theConnection) { |
// Create the NSMutableData that will hold |
// the received data |
// receivedData is declared as a method instance elsewhere |
receivedData=[[NSMutableData data] retain]; |
} else { |
// inform the user that the download could not be made |
} |
The download starts immediately upon receiving the initWithRequest:delegate: message. It can be canceled any time before the delegate receives a connectionDidFinishLoading: or connection:didFailWithError: message by sending the connection a cancel message.
When the server has provided sufficient data to create an NSURLResponse object, the delegate receives a connection:didReceiveResponse: message. The delegate method can examine the provided NSURLResponse and determine the expected content length of the data, MIME type, suggested filename and other metadata provided by the server.
It's important that the delegate be prepared to receive the connection:didReceiveResponse: message multiple times for a connection. This message can be sent due to server redirects, or in rare cases multi-part MIME documents. Each time the delegate receives the connection:didReceiveResponse: message, it should reset any progress indication and discard all previously received data. The example implementation in Listing 2 simply resets the length of the received data to 0 each time it is called.
Listing 2 Example connection:didReceiveResponse: implementation
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response |
{ |
// this method is called when the server has determined that it |
// has enough information to create the NSURLResponse |
// it can be called multiple times, for example in the case of a |
// redirect, so each time we reset the data. |
// receivedData is declared as a method instance elsewhere |
[receivedData setLength:0]; |
} |
As the connection progresses the delegate is sent connection:didReceiveData: messages as the data is received. The delegate implementation is responsible for storing the newly received data. In the example implementation in Listing 3, the new data is appended to the NSMutableData object created in Listing 1.
Listing 3 Example connection:didReceiveData: implementation
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data |
{ |
// append the new data to the receivedData |
// receivedData is declared as a method instance elsewhere |
[receivedData appendData:data]; |
} |
You can also use the connection:didReceiveData: method to provide an indication of the connection’s progress to the user.
If an error is encountered during the download, the delegate receives a connection:didFailWithError: message. The NSError object passed as the parameter specifies the details of the error. It also provides the URL of the request that failed in the user info dictionary using the key NSErrorFailingURLStringKey.
After the delegate receives a message connection:didFailWithError:, it receives no further delegate messages for the specified connection.
The example in Listing 4 releases the connection, as well as any received data, and logs the error.
Listing 4 Example connectionDidFailWithError: implementation
- (void)connection:(NSURLConnection *)connection |
didFailWithError:(NSError *)error |
{ |
// release the connection, and the data object |
[connection release]; |
// receivedData is declared as a method instance elsewhere |
[receivedData release]; |
// inform the user |
NSLog(@"Connection failed! Error - %@ %@", |
[error localizedDescription], |
[[error userInfo] objectForKey:NSErrorFailingURLStringKey]); |
} |
Finally, if the connection succeeds in downloading the request, the delegate receives the connectionDidFinishLoading: message. The delegate will receive no further messages for the connection and the NSURLConnection object can be released.
The example implementation in Listing 5 logs the length of the received data and releases both the connection object and the received data.
Listing 5 Example connectionDidFinishLoading: implementation
- (void)connectionDidFinishLoading:(NSURLConnection *)connection |
{ |
// do something with the data |
// receivedData is declared as a method instance elsewhere |
NSLog(@"Succeeded! Received %d bytes of data",[receivedData length]); |
// release the connection, and the data object |
[connection release]; |
[receivedData release]; |
} |
This represents the simplest implementation of a client using NSURLConnection. Additional delegate methods provide the ability to customize the handling of server redirects, authorization requests and caching of the response.
It’s not uncommon for a server to redirect a request for one URL to another URL. The NSURLConnection delegate will receive a connection: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 connection, create a new NSURLRequest for the connection, reject the redirect and have the connection return any data received from the NSURLResponse that caused the redirect, or cancel the download entirely.
To allow the redirect, the delegate should return the provided NSURLRequest. 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, the method should return nil.
Finally, the delegate can cancel the redirect and the connection, by sending the cancel message to connection.
The delegate will also receive 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 implementation in Listing 6 allows canonical changes and denies all server redirects.
Listing 6 Example connection:willSendRequest:redirectResponse: implementation.
-(NSURLRequest *)connection:(NSURLConnection *)connection |
willSendRequest:(NSURLRequest *)request |
redirectResponse:(NSURLResponse *)redirectResponse |
{ |
NSURLRequest *newRequest=request; |
if (redirectResponse) { |
newRequest=nil; |
} |
return newRequest; |
} |
If the delegate doesn't implement connection:willSendRequest:redirectResponse:, all canonical changes and server redirects are allowed.
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 NSURLConnection delegate receives a connection:didReceiveAuthenticationChallenge: message. In order for the connection to continue, the delegate must provide credentials to attempt to use for authentication, attempt to continue without credentials, or cancel the authentication request.
The NSURLAuthenticationChallenge instance 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.
Often the delegate prompts the user to enter a valid user name and password. If the authentication challenge has tried to authenticate and failed, the attempted credentials are returned by sending challenge a proposedCredential message. The delegate can then use these credentials to populate a dialog that it presents to the user.
Invoking previousFailureCount on the challenge parameter returns the number of authentication attempts. The delegate can provide this information to the end user, to determine if the credentials it 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 also choose to cancel the authentication challenge by sending cancelAuthenticationChallenge: to [challenge sender]. The delegate receives a connection:didCancelAuthenticationChallenge: message providing the opportunity to give the user feedback.
The example implementation in Listing 7 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 7 Example of connection:didReceiveAuthenticationChallenge: delegate method
-(void)connection:(NSURLConnection *)connection |
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]; |
} |
} |
If the delegate doesn’t implement connection:didReceiveAuthenticationChallenge: and the request requires authentication, valid credentials must already be available in the NSURLCredentialStorage or must be provided as part of the requested URL. If the credentials are not available or fail to authenticate, a continueWithoutCredentialForAuthenticationChallenge: message is sent by the underlying implementation.
By default the data for a connection is cached according to the support provided by the NSURLProtocol subclass that handles the request. An NSURLConnection Delegationdelegate can further refine that behavior by implementing connection:willCacheResponse:.
This delegate method can examine the provided NSCachedURLResponse object and change how the response is cached, perhaps restricting its storage to memory only or preventing it from being cached altogether. It is also possible to insert objects in an NSCachedURLResponse’s user info dictionary, causing them to be stored in the cache as part of the response.
connection:willCacheResponse: messages only for protocols that support caching.The example in Listing 8 prevents the caching of https responses. It also adds the current date to the user info dictionary for responses that are cached.
Listing 8 Example connection:withCacheResponse: implementation
-(NSCachedURLResponse *)connection:(NSURLConnection *)connection |
willCacheResponse:(NSCachedURLResponse *)cachedResponse |
{ |
NSCachedURLResponse *newCachedResponse=cachedResponse; |
if ([[[[cachedResponse response] URL] scheme] isEqual:@"https"]) { |
newCachedResponse=nil; |
} else { |
NSDictionary *newUserInfo; |
newUserInfo=[NSDictionary dictionaryWithObject:[NSCalendarDate date] |
forKey:@"Cached Date"]; |
newCachedResponse=[[[NSCachedURLResponse alloc] |
initWithResponse:[cachedResponse response] |
data:[cachedResponse data] |
userInfo:newUserInfo |
storagePolicy:[cachedResponse storagePolicy]] |
autorelease]; |
} |
return newCachedResponse; |
} |
Last updated: 2009-08-14