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 (несколько минут), чем на репортера ошибок (шесть месяцев).

Привет,

Ответы

Ответ 1

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

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

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

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

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

Ответ 2

Таймаут для DownloadTask вызывается NSURLSessionTaskDelegate, а не NSURLSessionDownloadDelegate

Чтобы запустить таймаут (-1001) во время загрузки:

Подождите, пока начнется загрузка. процентные куски загрузки данных вызовут:

URLSession: downloadTask: didWriteData: totalBytesWritten: totalBytesExpectedToWrite:

Затем PAUSE все приложение в отладчике 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__);
    }
}

Ответ 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,              

Ответ 4

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

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

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

Ответ 5

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