iOS Recipes - Matt Drance [64]
progressBlock:(PRPConnectionProgressBlock)progress
completionBlock:(PRPConnectionCompletionBlock)completion;
+ (id)connectionWithRequest:(NSURLRequest *)request
progressBlock:(PRPConnectionProgressBlock)progress
completionBlock:(PRPConnectionCompletionBlock)completion;
Unlike NSURLConnection, we don’t start the connections immediately. This is so we can further configure the connection before proceeding, as you’ll see momentarily. Explicit ‑start and ‑stop methods are provided to explicitly begin or cancel the connection.
So let’s take a closer look at the blocks. We’ve defined two: one for reporting incremental progress and another for reporting completion or failure. The completion block’s error parameter is passed on from the NSURLConnection delegate method ‑connection:didFailWithError:. If the connection finished successfully, the error is nil.
SimpleDownload/Classes/PRPConnection.h
typedef void (^PRPConnectionProgressBlock)(PRPConnection *connection);
typedef void (^PRPConnectionCompletionBlock)(PRPConnection *connection,
NSError *error);
Working with NSError
Our completion block checks the passed NSError parameter for nil in order to determine success. This is safe because the error is a known quantity; it is only non-nil when received in ‑connection:didFailWithError:, an explicit error condition.
This is very different from passing an NSError object by reference to an Apple API such as -[NSManagedObjectContext executeFetchRequest:error:]. In those cases, you must check the return value before inspecting the NSError object, as explained in the reference documentation for the API in question.
Since PRPConnection is a wrapper around NSURLConnection, it acts as the NSURLConnection’s delegate, saving data incrementally as the download progresses. As this happens, the progress block is invoked for every 1 percent change in progress. You can customize this frequency by setting the progressThreshold property. A value of 5, for example, means the block is invoked for every 5 percent change in progress. This allows you to easily present PRPConnection progress in a number of different ways. If you don’t care about progress, just pass a nil progress block when creating the connection.
The contentLength property represents the Content-Length value in the response header of the connection. This value is set when we receive the standard ‑connection:didReceiveResponse: NSURLConnection delegate method.
SimpleDownload/Classes/PRPConnection.m
- (void)connection:(NSURLConnection *)connection
didReceiveResponse:(NSURLResponse *)response {
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if ([httpResponse statusCode] == 200) {
NSDictionary *header = [httpResponse allHeaderFields];
NSString *contentLen = [header valueForKey:@"Content-Length"];
NSInteger length = self.contentLength = [contentLen integerValue];
self.downloadData = [NSMutableData dataWithCapacity:length];
}
}
}
We use this value, along with the latest size of the downloaded data, to compute the percentComplete property.
SimpleDownload/Classes/PRPConnection.m
- (float)percentComplete {
if (self.contentLength <= 0) return 0;
return (([self.downloadData length] * 1.0f) / self.contentLength) * 100;
}
We invoke the progress block whenever percentComplete reaches a multiple of the specified threshold.
SimpleDownload/Classes/PRPConnection.m
- (void)connection:(NSURLConnection *)connection
didReceiveData:(NSData *)data {
[self.downloadData appendData:data];
float pctComplete = floor([self percentComplete]);
if ((pctComplete - self.previousMilestone) >= self.progressThreshold) {
self.previousMilestone = pctComplete;
if (self.progressBlock) self.progressBlock(self);
}
}
Using PRPConnection is easy: create one by calling +downloadWithURL:progressBlock:completionBlock:, and then call ‑start. PRPConnection takes care of all the heavy lifting, leaving us to handle only the events we care about. When the download is finished, just access the