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