Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I use HTTP caching with an NSURLSessionDownloadTask on iOS?

Tags:

ios

I'm trying to use NSURLSessionDownloadTask, and take advantage of Apple's in-built URL caching functionality. I have succeeded in getting the caching to work when using an NSURLSessionDataTask using the code below:

- (void)downloadUsingNSURLSessionDataTask:(NSURL *)url {
    NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request];
    [dataTask resume];
}

- (void)cachedDataTaskTest {
    // This call performs an HTTP request
    [self downloadUsingNSURLSessionDataTask:[NSURL URLWithString:@"http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"]];
    [NSThread sleepForTimeInterval:1];

    // This call returns the locally cached copy, and no HTTP request occurs
    [self downloadUsingNSURLSessionDataTask:[NSURL URLWithString:@"http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"]];
}

However, I need to perform a background download for which I have to use an NSURLDownloadTask. When I switch to this the caching behaviour does not occur.

- (void)downloadUsingNSURLSessionDownloadTask:(NSURL *)url {
    NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request];
    [downloadTask resume];
}

- (void)cachedDownloadTaskTest {
    // This call performs an HTTP request
    [self downloadUsingNSURLSessionDownloadTask:[NSURL URLWithString:@"http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"]];
    [NSThread sleepForTimeInterval:1];

    // This call also performs an HTTP request
    [self downloadUsingNSURLSessionDownloadTask:[NSURL URLWithString:@"http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"]];
}

This documentation from Apple indicates that NSURLDownloadTasks don't call the URLSession:dataTask:willCacheResponse:completionHandler: delegate method, so it is not possible for your app to hook into the caching life cycle. My guess is that this implies that caching is simply not available for these tasks, but it is not explicit about this.

  1. For a data task, the NSURLSession object calls the delegate’s URLSession:dataTask:willCacheResponse:completionHandler: method. Your app should then decide whether to allow caching. If you do not implement this method, the default behavior is to use the caching policy specified in the session’s configuration object.

Can anyone confirm this hunch that NSURLSessionDownloadTasks simply don't support caching? Is it possible to take advantage of Apple's HTTP caching behaviour in a background task?

like image 326
Nick Street Avatar asked Oct 03 '14 07:10

Nick Street


People also ask

What is Nscache?

A mutable collection you use to temporarily store transient key-value pairs that are subject to eviction when resources are low.

What is NSURLSession?

The NSURLSession class and related classes provide an API for downloading data from and uploading data to endpoints indicated by URLs. Your app can also use this API to perform background downloads when your app isn't running or, in iOS, while your app is suspended.


2 Answers

NSURLSessionDownloadTask performs work using a system service (daemon) that performs the download outside your application process. Because of this, the delegate callbacks that actually get invoked for a download task are more limited than those for NSURLSessionDataTask. As documented in Life Cycle of a URL Session, a data task delegate will receive callbacks to customize caching behavior, while a download task delegate will not.

A download task should use the caching policy specified by the NSURLRequest, and should use the cache storage specified by the NSURLSessionConfiguration (if it does not, file a bug). The default cache policy is NSURLRequestUseProtocolCachePolicy, and the default URL cache storage is the shared URL cache for non-background and non-ephemeral configurations. The delegate callbacks for URLSession:dataTask:willCacheResponse:completionHandler: are not a good indicator of wether caching is actually occurring.

If you create an NSURLSessionDownloadTask using the default session configuration and do not customize the cache policy of NSURLRequests, caching is already happening.

like image 167
quellish Avatar answered Nov 06 '22 06:11

quellish


It looks like NSURLSessionDownloadTask does not cache, by design.

NSURLSessionConfiguration documentation

The defaultSessionConfiguration method is documented:

The default session configuration uses a persistent disk-based cache (except when the result is downloaded to a file) and stores credentials in the user’s keychain.

However, none of the other constructors are documented to exclude the above italicized exception. I also tested backgroundSessionConfigurationWithIdentifier and it doesn't appear to do the job either.

Also, requestCachePolicy doesn't offer any way out of the exception either.

NSURLSessionDownloadTask runtime efficiency

NSURLSessionDownloadTask writes incoming data to a temporary file. When it completes the file, it notifies the delegate or completion handler. Finally it deletes the file.

While it could simply move the file into cache at the end, it would have to deal with either the delegate or completion handler actually modifying the file and thus changing its cached representation, or even moving the file to a permanent location it can't track.

It could copy the file before notifying the delegate or completion handler, but this would be inefficient for large files.

It could keep the file read-only, but doesn't appear to do so on iOS 8.0.

Therefore, it's unlikely that the system would do any caching of download tasks.

Workaround

Your best bet is use NSURLSessionDataTask, then when your delegate's URLSession:dataTask:didReceiveData: method is called, append the incoming data to your own file. The next time you use NSURLSessionDataTask you get the cached data all in one call of URLSession:dataTask:didReceiveData:.

like image 20
Glen Low Avatar answered Nov 06 '22 07:11

Glen Low