How to implement resumable file downloads on iPhone SDK

iphone_sdk_codeFile downloading is a feature used very often in various applications. You may need to get an update from the server, to provide optional resources to the user or to get a copy of a shared document — all this functionality deals with file downloads. And as long as downloads go over network they can be interrupted which is very frustrating, especially if you are downloading something big that takes a lot of time.

Modern applications can’t afford forcing users to start interrupted downloads from the beginning. In Mac OS X SDK we have a very nice NSURLDownload class that supports download resuming functionality. This class is not a part of iPhone SDK though. That’s why we have to descend one level lower and look into what HTTP protocol provides.

Each HTTP request includes a number of headers that define what kind of response we want to get. This may include desired character set, language, encoding etc. If server respects those headers it will customize the response accordingly. One of the standard headers named «Range» allows to define which part of the response (in bytes) we want to receive. Here are some examples of using the Range header:

Range: bytes=0-9

This tells the server to return 10 bytes from 0 to 9 inclusively.

Range: bytes=101-200

This tells the server to return 100 bytes from 101 to 200.

Range: bytes=256-

This tells the server to return all the bytes starting from byte 256.

Range header is just what we need because it tells the server to return only a part of the response it would normally send. If we remember how many bytes we have already downloaded (which we can find out by measuring partial file size) we can tell the server to provide us the rest. Here is the example of such HTTP request:

GET /somefile.txt HTTP/1.1
Host: www.example.com
Range: bytes=25000-

Assuming we have already downloaded the first 25000 bytes of the file this request will ask for the rest. Other headers may also be included in this request but we don’t show it here. As soon as we get the response we can append it to the partially downloaded file. Here is a code snippet that uses NSURLConnection (a class that does exist in iPhone SDK) and implements resumed download:

- (void)startDownload:(NSString *)fileURL withOffset:(int)offset {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:fileURL]];

NSString *range = @"bytes=";
range = [range stringByAppendingString:[[NSNumber numberWithInt:offset] stringValue]];
range = [range stringByAppendingString:@"-"];
[request setValue:range forHTTPHeaderField:@"Range"];

connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
if (connection) {
[connection start];
} else {
// Handle error
}
}

This method takes offset as a parameter which is the length of existing partial download. Of course you will also need to implement delegate methods to do something with the data you receive from server. For example you may want to write it to the file immediately:

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSFileHandle handle = [NSFileHandle fileHandleForWritingAtPath:localFilePath];
[handle seekToEndOfFile];
[handle writeData:data];
[handle closeFile];
}

You may also want to cache it and write to the file later when you have enough data. This depends on what makes more sense for your solution. Happy coding :-)