20

I am using NSURLSessionDownloadTask with background sessions to achieve all my REST requests. This way I can use the same code without have to think about my application being in background or in foreground.

My back-end has been dead for a while, and I have taken that opportunity to test how does NSURLSession behave with timeouts.

To my utter surprise, none of my NSURLSessionTaskDelegate callbacks ever gets called. Whatever timeout I set on the NSURLRequest or on the NSURLSessionConfiguration, I never get any callback from iOS telling me that the request did finish with timeout.

That is, when I start a NSURLSessionDownloadTask on a background session. Same behavior happens the application is in background or foreground.

Sample code:

- (void)launchDownloadTaskOnBackgroundSession {
    NSString *sessionIdentifier = @"com.mydomain.myapp.mySessionIdentifier";
    NSURLSessionConfiguration *backgroundSessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfiguration:sessionIdentifier];
    backgroundSessionConfiguration.requestCachePolicy = NSURLRequestReloadIgnoringCacheData;
    backgroundSessionConfiguration.timeoutIntervalForRequest = 40;
    backgroundSessionConfiguration.timeoutIntervalForResource = 65;
    NSURLSession *backgroundSession = [NSURLSession sessionWithConfiguration:backgroundSessionConfiguration delegate:self delegateQueue:[NSOperationQueue mainQueue]];

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://www.timeout.com/"]];
    request.timeoutInterval = 30;
    NSURLSessionDownloadTask *task = [backgroundSession downloadTaskWithRequest:request];
    [task resume];
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    NSLog(@"URLSession:task:didCompleteWithError: id=%d, error=%@", task.taskIdentifier, error);
}

However, when I use the default session, then I do get an error callback after 30seconds (the timeout that I set at request level).

Sample code:

- (void)launchDownloadTaskOnDefaultSession {
    NSURLSessionConfiguration *defaultSessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
    defaultSessionConfiguration.requestCachePolicy = NSURLRequestReloadIgnoringCacheData;
    defaultSessionConfiguration.timeoutIntervalForRequest = 40;
    defaultSessionConfiguration.timeoutIntervalForResource = 65;
    NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration:defaultSessionConfiguration delegate:self delegateQueue:[NSOperationQueue mainQueue]];

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://www.timeout.com/"]];
    request.timeoutInterval = 30;
    NSURLSessionDownloadTask *task = [defaultSession downloadTaskWithRequest:request];
    [task resume];
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    NSLog(@"URLSession:task:didCompleteWithError: id=%d, error=%@", task.taskIdentifier, error);
}

I cannot seem to find in the documentation anything that suggests that the timeout should behave differently when using background sessions.

Has anyone bumped into that issue as well? Is that a bug or a feature?

I am considering creating a bug report, but I usually get feedback much faster on SO (a few minutes) than on the bug reporter (six months).

Regards,

4

5 回答 5

21

从 iOS8 开始,后台模式下的 NSUrlSession 如果服务端没有响应,就不会调用这个委托方法。 -(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
下载/上传无限期地保持空闲。当服务器没有响应时,在 iOS7 上调用此委托时会出错。

一般来说,如果网络上出现问题,NSURLSession 后台会话不会使任务失败。相反,它会继续寻找运行请求的好时机并在那时重试。这种情况一直持续到资源超时到期(即用于创建会话的 NSURLSessionConfiguration 对象中 timeoutIntervalForResource 属性的值)。该值的当前默认值为一周!

引用自此来源的信息

换句话说,iOS7 中超时失败的行为是不正确的。在后台会话的上下文中,不因为网络问题而立即失败更有趣。所以从 iOS8 开始,NSURLSession 任务即使遇到超时和网络丢失也会继续。但是,它会一直持续到达到 timeoutIntervalForResource 为止。

所以基本上 timeoutIntervalForRequest 不会在后台会话中工作,但 timeoutIntervalForResource 会。

于 2016-02-12T08:02:05.993 回答
4

DownloadTask 的超时由 NSURLSessionTaskDelegate 而不是 NSURLSessionDownloadDelegate 抛出

在下载任务期间触发超时(-1001):

等到下载开始。数据下载的百分比块将触发:

URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:

然后在 XCode 调试器中暂停整个应用程序。

等待 30 秒。

使用 XCode 调试器按钮取消暂停应用

来自服务器的 http 连接应该超时并触发:

-1001 "请求超时。"

#pragma mark -
#pragma mark NSURLSessionTaskDelegate - timeouts caught here not in DownloadTask delegates
#pragma mark -
- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{

    if(error){

        ErrorLog(@"ERROR: [%s] error:%@", __PRETTY_FUNCTION__,error);

        //-----------------------------------------------------------------------------------
        //-1001 "The request timed out." 
//        ERROR: [-[SNWebServicesManager URLSession:task:didCompleteWithError:]] error:Error Domain=NSURLErrorDomain Code=-1001 "The request timed out." UserInfo={NSUnderlyingError=0x1247c42e0 {Error Domain=kCFErrorDomainCFNetwork Code=-1001 "(null)" UserInfo={_kCFStreamErrorCodeKey=-2102, _kCFStreamErrorDomainKey=4}}, NSErrorFailingURLStringKey=https://directory.clarksons.com/api/1/dataexport/ios/?lastUpdatedDate=01012014000000, NSErrorFailingURLKey=https://directory.clarksons.com/api/1/dataexport/ios/?lastUpdatedDate=01012014000000, _kCFStreamErrorDomainKey=4, _kCFStreamErrorCodeKey=-2102, NSLocalizedDescription=The request timed out.}
        //-----------------------------------------------------------------------------------


    }else{
        NSLog(@"%s SESSION ENDED NO ERROR - other delegate methods should also be called so they will reset flags etc", __PRETTY_FUNCTION__);
    }
}
于 2016-01-18T16:04:55.820 回答
3

UIApplicationDelegate 中有一种方法可以让你了解后台进程。

-(void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler

如果有多个会话,您可以通过以下方式识别您的会话

if ([identifier isEqualToString:@"com.mydomain.myapp.mySessionIdentifier"]) 

另一种方法用于定期通知进度。在这里您可以检查 NSURLSession 的状态

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite


NSURLSessionTaskStateRunning = 0,                   
NSURLSessionTaskStateSuspended = 1,
NSURLSessionTaskStateCanceling = 2,                   
NSURLSessionTaskStateCompleted = 3,              
于 2014-04-25T11:15:24.610 回答
3

和你一样,我正在开发的应用程序总是使用后台会话。我注意到的一件事是,如果它中断工作连接,则超时工作正常,即传输成功启动。但是,如果我为不存在的 URL 启动下载任务,它不会超时。

鉴于您说您的后端已经死了一段时间,这听起来很像您所看到的。

很容易重现。只需将超时设置为 5 秒。使用有效的 URL,您将获得一些进度更新,然后看到它超时。即使有后台会话。使用无效的 URL,只要您调用 resume,它就会安静下来。

于 2015-03-02T04:45:05.710 回答
0

我遇到了完全相同的问题。我发现的一种解决方案是使用两个会话,一个用于使用默认配置的前台下载,一个用于使用后台配置的后台下载。当更改为背景/前景时,生成恢复数据并将其从一个传递到另一个。但我想知道您是否找到了另一种解决方案。

于 2015-08-27T09:07:15.577 回答