NSURLSessionTask никогда не перезванивает после тайм-аута при использовании фоновой конфигурации

Я использую NSURLSessionDownloadTask с фоновыми сессиями для выполнения всех моих запросов REST. Таким образом, я могу использовать один и тот же код, не думая о том, что мое приложение находится в фоновом режиме или на переднем плане.

Мой бэкэнд уже давно мертв, и я воспользовался этой возможностью, чтобы проверить, как NSURLSession вести себя с таймаутами.

К моему полному удивлению, ни один из моих NSURLSessionTaskDelegate обратные вызовы когда-либо вызывается. Какой бы тайм-аут я установил на NSURLRequest или на NSURLSessionConfigurationЯ никогда не получаю никакого обратного вызова от iOS, говорящего мне, что запрос закончился с истечением времени ожидания.

То есть когда я начинаю NSURLSessionDownloadTask на фоновой сессии. Такое же поведение происходит, если приложение находится в фоновом режиме или на переднем плане.

Образец кода:

- (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);
}

Однако, когда я использую сеанс по умолчанию, я получаю сообщение об ошибке через 30 секунд (время ожидания, которое я установил на уровне запроса).

Образец кода:

- (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);
}

Я не могу найти в документации ничего, что бы указывало на то, что тайм-аут должен вести себя по-другому при использовании фоновых сессий.

Кто-нибудь тоже сталкивался с этой проблемой? Это ошибка или особенность?

Я подумываю о создании отчета об ошибках, но обычно я получаю обратную связь гораздо быстрее на SO (несколько минут), чем на репортера ошибок (шесть месяцев).

С Уважением,

5 ответов

Решение

Начиная с iOS8, NSUrlSession в фоновом режиме не вызывает этот метод делегата, если сервер не отвечает.-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
Загрузка / выгрузка остаются бездействующими бесконечно. Этот делегат вызывается на iOS7 с ошибкой, когда сервер не отвечает.

В общем, фоновый сеанс NSURLSession не завершает задачу, если что-то идет не так по сети. Скорее, он продолжает искать подходящее время для выполнения запроса и повторных попыток. Это продолжается, пока не истечет время ожидания ресурса (то есть значение свойства timeoutIntervalForResource в объекте NSURLSessionConfiguration, который вы используете для создания сеанса). Текущее значение по умолчанию для этого значения составляет одну неделю!

Цитируемая информация взята из этого источника

Другими словами, поведение сбоя в течение тайм-аута в iOS7 было неправильным. В контексте фонового сеанса более интересно не сразу потерпеть неудачу из-за проблем с сетью. Таким образом, начиная с iOS8, задача NSURLSession продолжается, даже если она сталкивается с таймаутами и потерей сети. Однако это продолжается до тех пор, пока не будет достигнуто значение timeoutIntervalForResource.

Поэтому timeoutIntervalForRequest не будет работать в фоновом сеансе, а timeoutIntervalForResource будет работать.

Время ожидания для 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__);
    }
}

Как и вы, приложение, над которым я работаю, всегда использует фоновую сессию. Одна вещь, которую я заметил, заключается в том, что таймаут работает правильно, если он прерывает рабочее соединение, т. Е. Передача началась успешно. Однако, если я начну задачу загрузки для несуществующего URL-адреса, время не истечет.

Учитывая, что вы сказали, что ваш бэкэнд уже давно мертв, это очень похоже на то, что вы видели.

Это довольно легко воспроизвести. Просто установите тайм-аут на 5 секунд. С действительным URL вы получите некоторые обновления прогресса, а затем увидите время ожидания. Даже с фоновой сессией. С недопустимым URL-адресом он просто замолкает, как только вы вызываете резюме.

В 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,              

Я подошел к той же самой проблеме. Одно решение, которое я нашел, состоит в том, чтобы использовать два сеанса, один для загрузок переднего плана с использованием конфигурации по умолчанию и один для фоновых загрузок с фоновой конфигурацией. При переходе на задний план / передний план генерируйте данные резюме и передавайте их от одного к другому. Но мне интересно, если вы нашли другое решение.

Другие вопросы по тегам