Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSURLSessionDownloadTask keeps retrying when using Background Configuration

I have a problem when it comes to a slow backend and downloading data with background configuration.

NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration backgroundSessionConfiguration:identifier];
_backgroundSession = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil];
NSURLSessionDownloadTask *downloadTask = [_backgroundSession downloadTaskWithURL:URL];
[downloadTask resume];

If the connection is astablished but it takes more than 60 seconds for it to send back data a timeout occurs. That is fine. However the behaviour I experience is that I don't get an Error. The Session just sends out a new request. "Give me data again". I have no idea where this happens. Not in my code and no delegate methods are called that I'm aware of. I just have access to the server logs. It takes the server roughly 68 seconds to send back data but the app just ignores it because it's waiting for the new request.

One solution is to increase the timeout value. But I don't like it and it only works for iOS 7. Not iOS 8.

sessionConfig.timeoutIntervalForRequest = 10 * 60.0;

Does anyone have any insight in this? I found this link about timeout issue for background session here on stackoverflow. It's 10 months old but no solutions, only people agreeing.

like image 465
Niclas Eriksson Avatar asked Oct 15 '14 09:10

Niclas Eriksson


3 Answers

Since iOS8, the NSUrlSession in background mode does not call this delegate method if the server does not respond. -(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error The download/upload remains idle indefinitely. This delegate is called on iOS7 with an error when the server does not respond.

In general, an NSURLSession background session does not fail a task if something goes wrong on the wire. Rather, it continues looking for a good time to run the request and retries at that time. This continues until the resource timeout expires (that is, the value of the timeoutIntervalForResource property in the NSURLSessionConfiguration object you use to create the session). The current default for that value is one week! In other words, the behaviour of failing for a timeout in iOS7 was incorrect. In the context of a background session, it is more interesting to not fail immediately because of network problems. So since iOS8, NSURLSession task continues even if it encounters timeouts and network loss. It continues however until timeoutIntervalForResource is reached.

So basically timeoutIntervalForRequest won't work in Background session but timeoutIntervalForResource will.

Source: Apple Forum

like image 160
Utsav Dusad Avatar answered Oct 22 '22 16:10

Utsav Dusad


Could not stop myself, here is your answer a bit refactored :)

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
NSURLSessionConfiguration *sessionConfig;
float timeout = 5 * 60.0f;

BOOL iOS8OrNewer = [[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0;
if (iOS8OrNewer) {
    sessionConfig = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:identifier];
    request.timeoutInterval = timeout;
}
else {
    sessionConfig = [NSURLSessionConfiguration backgroundSessionConfiguration:identifier];
    sessionConfig.timeoutIntervalForRequest = timeout;
}

_backgroundSession = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil];

NSURLSessionDownloadTask *downloadTask = [_backgroundSession downloadTaskWithRequest:request];
like image 6
Sunkas Avatar answered Oct 22 '22 16:10

Sunkas


I managed to solve it. I'm not saying my solution is the solution but it's a solution.

The behaviour I'm experiencing is that iOS 7 and iOS 8 prioritises properties differently. I have two places to set these timeout properties, the NSURLSessionConfiguration and in the NSMutableURLRequest. iOS 7 couldn't care less about the requests property and iOS 8 couldn't care less about the configurations property. Right now my solution looks like this:

NSURLSessionConfiguration *sessionConfig;
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) {
    sessionConfig = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:identifier];
}
else {
    sessionConfig = [NSURLSessionConfiguration backgroundSessionConfiguration:identifier];
}
if ([[[UIDevice currentDevice] systemVersion] floatValue] < 8.0) {
    sessionConfig.timeoutIntervalForRequest = 5 * 60.0;
}
_backgroundSession = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil];

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) {
    request.timeoutInterval = 5 * 60.0;
}
NSURLSessionDownloadTask *downloadTask = [_backgroundSession downloadTaskWithRequest:request];

I guess one could just not do the systemVersion checks and set both properties to the same value. Although I strongly believe that less code is more I believe even more strongly in not affecting states that does not need to be affected. However I would love to hear peoples opinions. And if someone has a better solution please share.

Oh one more thing. The retrying part will keep happening until timeoutIntervalForResource reaches its default value of 7 days according to the documentation. I have decreased this to 10 minutes.

sessionConfig.timeoutIntervalForResource = 10 * 60;

I'm not saying it should be changed. This is a decision we made for our specific environment setup.

Update

We changed the timeoutIntervalForResource back to the default value of 7 days. We have customers in China for instance and some of them have really poor connection. A master limit of 10 minutes was just silly.

Make sure to check out Sunkas answer for better code quality. My code snippet, however, is spread out over different classes so I can't reuse that approach 100 %.

like image 3
Niclas Eriksson Avatar answered Oct 22 '22 17:10

Niclas Eriksson