viernes, 18 de junio de 2010

WebServices solution for iPhone - First Stage

The FileDownload Class

After reviewing some examples of how to download a file from an URL and after implementing it by myself, I came to the idea to do it as a separate class. The objective of the new class was to handle the connection and processing needed to do the job.

The published methods are:

- (FileDownload *) initWithURL:(NSString *)url callBack:(id<FileDownloadProtocol>)delegate;


This initializer is used to immediately start the download process given the desired URL to download and the delegate, which is the object which will receive the downloaded data. Its implementation is as follows:

/**

Initialize with file to download

**/

- (FileDownload *)initWithURL:(NSString *)url callBack:(id<FileDownloadProtocol>)delegate

{

[self init];

[self startDownloadFile:url callBack:delegate];

return self;

}


I’m not checking if the object was created successfully before starting the download, that’s a to do activity for one day.

- (void)startDownloadFile:(NSString *)url callBack:(id<FileDownloadProtocol>)delegate;


This method is used to start the download process given the desired URL to download and the delegate, which is the object which will receive the downloaded data. Internally, this method is called from the initializer, when the initializer is used. Its implementation is as follows:

/**

Call to download file

**/

- (void)startDownloadFile:(NSString *)url callBack:(id<FileDownloadProtocol>)delegate

{

processing = YES;

callBackObject = delegate;

NSURLRequest *xmlURLRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];

self.dataFeedConnection = [[[NSURLConnection alloc] initWithRequest:xmlURLRequest delegate:self] autorelease];

if (self.dataFeedConnection == nil)

{

NSDictionary *userInfo = [NSDictionary dictionaryWithObject:NSLocalizedString(@"No Connection Error", @"Error message displayed when not connected to the Internet.") forKey:NSLocalizedDescriptionKey];

NSError *noConnectionError = [NSError errorWithDomain:NSCocoaErrorDomain code:kCFURLErrorNotConnectedToInternet userInfo:userInfo];

[self handleError:noConnectionError];

}

[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;

}


Basically it is the well know recipe to download a file.

-(void)stopDownload;


This method is used to cancel any active download. If there is no active download, nothing is done. Its implementation is as follows:

/**

Stop actual download process

**/

-(void)stopDownload

{

processing = NO;

if (dataFeedConnection != nil)

{

[dataFeedConnection cancel];

}

}


I’m using the processing variable to know if something is happening and avoid calling downloadError method once the download was cancelled.

One important property of the class is:

@property (nonatomic, assign) int jobID;


This property is a logical way to identify the current download, used for the case when more than 1 instance of the class is used a same time.

The class implements the next protocol

@protocol FileDownloadProtocol

/**

Notify an error

**/

- (void) downloadError:(NSError *)error downloadID:(NSInteger)jobID;


/**

Notify download complete

**/

- (void) downloadSucceed:(NSMutableData *)data downloadID:(NSInteger)jobID;

@end


Both methods defined in the protocol are required. The protocol is used to define the delegate object that will handle the received data.

The protocol’s methods are called once the process is finished or an error happens. There are 2 options:

- (void) downloadError:(NSError *)error downloadID:(NSInteger)jobID;


This method is called back to the delegate when an error occurs. Currently, there is no way to propagate the kind of error raised, but for the class implementation I only need to know if the download was successfully or not. This is only called from the error handler method:

- (void)handleError:(NSError *)error

{

if (processing && callBackObject != nil)

{

// Call the error Callback

[callBackObject downloadError:error downloadID:jobID];

}

}


Thanks to processing, it is only called when a processing is active.

- (void) downloadSucceed:(NSMutableData *)data downloadID:(NSInteger)jobID;


This method is called when the download succeed; with this call, the class is passing the downloaded information to the delegate. This is called from the NSURLConnection delegate method connectionDidFinishLoading:

/**

Connection finishes

**/

- (void)connectionDidFinishLoading:(NSURLConnection *)connection

{

self.dataFeedConnection = nil;

[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;

if (processing && callBackObject != nil)

{

// Call the Succeed Callback

[callBackObject downloadSucceed:dataData downloadID:jobID];

}

self.dataData = nil;

}


Internally, the class handles memory and the NSURLConnection delegate methods. As usual, the implemented methods are:

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;

- (void)connectionDidFinishLoading:(NSURLConnection *)connection;


Any object willing to use FileDownload has 2 options:
1. Create the FileDownload object with initWithURL
2. Create the FileDownload object, and then start the download process using startDownloadFile

Sample implementation might be:

self.downloadObject = [[FileDownload alloc] init];

[self.downloadObject setJobID:theID];

[self.downloadObject startDownloadFile:theURL callBack:self];

Then just wait for the FileDownloadProtocol’s methods to be called.

This class is a Soot-and-Forget download, at least I call it that way, because once the download method is called, the application is free to do any other processing until the downloaded information is ready or a download error is raised.

I now use this class whenever I need to download data from a given URL.

In the next post of this series I'll talk about the FileDownloadController class.

No hay comentarios: