NSURLSession: метод dataTaskWithRequest никогда не достигает обратного вызова завершения при длительных ответах

#ios #objective-c #nsurlsession

Вопрос:

Следующий код, который использовался для создания сеанса связи с удаленным сервером и отправки/получения HTTP-запросов/ответов.

Однако, когда к ответу прикреплен большой файл, блок обратного вызова так и не был достигнут.

Только при явном вызове метода cancel после некоторого времени ожидания от задачи NSURLSession (_dataTask) этот обратный вызов вызывается.

обратите внимание, что с помощью tcpdump можно легко заметить, что ответ был правильно получен на стороне клиента.

 NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1;

NSURLSession* session = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:queue];

_dataTask = [session dataTaskWithRequest:req completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    if ([error code] == NSURLErrorCancelled) {
        writeLog(LOG_ERROR, "NSURLErrorCancelled");
    } else {
        ...
    }
}];

[_dataTask resume]

// after timeout, the operation is cancelled.
sleep(100)
[_dataTask cancel];
 

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

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

Спасибо!

Комментарии:

1. я думаю, что вы упускаете здесь dispatch_async(dispatch_get_main_queue() ответ.

2. …но в нем нет блока асинхронного завершения . У него действительно есть один

Ответ №1:

При извлечении больших ресурсов следует использовать задачу загрузки. Задача обработки данных попытается загрузить весь ответ в один NSData объект. Одновременная загрузка большого ресурса в память не только неэффективна, но и, если он необычайно велик, может вызвать проблемы.

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

Ты сказал:

Я видел, что в NSURLSession выделенном для загрузки файлов есть альтернативный метод, называемый downloadTaskWithRequest , но у него нет блока асинхронного завершения.

Два замечания:

  1. Существует исполнение, dataTaskWithRequest:completionHandler: , которое имеет блок завершения:
     NSURLSession* session = [NSURLSession sharedSession];
    NSURLSessionDownloadTask *task = [session downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        ...
    }];
    [task resume];
     

    Обратите внимание, я бы посоветовал использовать sharedSession , если вы не устанавливаете делегата или иным образом не настраиваете свой NSURLSession . Вы не хотите создавать экземпляры NSURLSession объектов без необходимости. И если вам действительно необходимо создать экземпляр a NSURLSession , повторно используйте его для последующих задач и/или обязательно позвоните finishTasksAndInvalidate после отправки последней задачи для этого сеанса, иначе NSURLSession произойдет утечка. И, если вы создадите свой собственный NSURLSession экземпляр , вам не нужно создавать собственную очередь операций, так как по умолчанию она создаст для вас последовательную очередь, если вы не предоставите очередь операций.

  2. Исполнение без параметра блока, downloadTaskWithURL: тоже работает. Все, что вам нужно сделать, это указать a delegate для вашего NSURLSession , а затем и реализовать URLSession:downloadTask:didFinishDownloadingToURL: .

    Причина, по которой я предлагаю это, заключается в том, что часто, когда мы загружаем очень большие ресурсы (особенно по сотовой связи), мы понимаем, что пользователи могут захотеть покинуть наше приложение и завершить загрузку в фоновом режиме. В таких ситуациях мы бы использовали предысторию NSURLSessionConfiguration . И при использовании фоновых сеансов вы должны использовать этот подход, основанный на делегировании. Итак, если вы думаете, что в конечном итоге сможете использовать фоновые сеансы для длительных загрузок, то принятие подхода, основанного на делегировании, сейчас-неплохая идея.

    Дополнительные сведения см. в разделе Загрузка файлов в фоновом режиме.