Как использовать NSOperationQueue с NSURLSession?
Я пытаюсь создать загрузчик объемного изображения, где изображения могут быть добавлены в очередь "на лету" для загрузки, и я могу узнать о прогрессе и когда они будут загружены.
Посредством моего чтения кажется, что функция NSOperationQueue
для функции очереди и NSURLSession
для сетевых функций кажется моей лучшей ставкой, но я смущен тем, как использовать их в тандеме.
Я знаю, что добавляю экземпляры NSOperation
в NSOperationQueue
, и они попадают в очередь. И, кажется, я создаю задачу загрузки с NSURLSessionDownloadTask
и несколько, если мне нужно несколько задач, но я не уверен, как я поместил их вместе.
NSURLSessionDownloadTaskDelegate
похоже, имеет всю необходимую информацию для загрузки и завершения уведомлений, но мне также нужно иметь возможность остановить определенную загрузку, остановить все загрузки и обработать данные, которые я получаю от загрузки.
Ответы
Ответ 1
Ваша интуиция здесь правильная. Если выдавать много запросов, наличие NSOperationQueue
с maxConcurrentOperationCount
из 4 или 5 может быть очень полезным. В отсутствие этого, если вы выдаете много запросов (например, 50 больших изображений), вы можете испытывать проблемы с тайм-аутом при работе с медленным сетевым соединением (например, некоторыми сотовыми соединениями). Операционные очереди имеют и другие преимущества (например, зависимости, назначение приоритетов и т.д.), Но контроль степени concurrency является ключевым преимуществом ИМХО.
Если вы используете запросы на основе completionHandler
, внедрение решения на основе операций довольно тривиально (это типичная параллельная реализация подкласса NSOperation
, см. раздел "Конфигурирование операций для параллельного выполнения" Очереди операций в руководстве по программированию concurrency для получения дополнительной информации).
Если вы используете реализацию на основе delegate
, все начинает быстро становиться довольно волосатым. Это объясняется понятной (но невероятно раздражающей) функцией NSURLSession
, при которой делегаты на уровне задач реализуются на уровне сеанса. (Подумайте об этом: два разных запроса, требующих различной обработки, вызывают один и тот же метод делегата на общем объекте сеанса. Egad!)
Можно выполнить завершение работы NSURLSessionTask
на основе делегата в операции (я и другие, я уверен, это сделал), но это связано с громоздким процессом, заключающимся в том, что объект сеанса поддерживает перекрестные ссылки на словари, ссылающиеся на идентификаторы задач с объектами операции задачи, передать эти методы делегирования задачи, переданные объекту задачи, а затем объекты задач соответствуют различным протоколам делегатов NSURLSessionTask
. Это довольно значительная работа, потому что NSURLSession
не предоставляет функцию maxConcurrentOperationCount
-style в сеансе (не говоря уже о друге NSOperationQueue
, как зависимости, блоки завершения и т.д.).
И стоит отметить, что операционная реализация - это немного не стартер с фоновыми сеансами. Ваши задачи загрузки/загрузки будут продолжать работать после того, как приложение будет прекращено (что хорошо, это довольно существенное поведение в фоновом запросе), но когда ваше приложение перезагружается, очередь операций и все его действия исчезли, Поэтому для фоновых сеансов вам необходимо использовать чистую реализацию NSURLSession
на основе делегатов.
Ответ 2
Концептуально NSURLSession - это очередь операций. Если вы возобновите задачу NSURLSession и точку останова в обработчике завершения, трассировка стека может быть довольно показательной.
Вот выдержка из верного учебника Ray Wenderlich по NSURLSession с добавленным оператором NSLog
для точки останова при выполнении обработчика завершения:
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:[NSURL URLWithString:londonWeatherUrl]
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
// handle response
NSLog(@"Handle response"); // <-- breakpoint here
}] resume];
![NSOperationQueue Serial Queue breakpoint]()
Выше мы видим, что обработчик завершения выполняется в Thread 5 Queue: NSOperationQueue Serial Queue
.
Итак, я предполагаю, что каждая NSURLSession поддерживает свою собственную очередь операций, и каждая задача, добавленная в сеанс, - под капотом - выполняется как NSOperation. Поэтому нет смысла поддерживать очередь операций, которая контролирует объекты NSURLSession или задачи NSURLSession.
NSURLSessionTask уже предлагает эквивалентные методы, такие как cancel
, resume
, suspend
и т.д.
Это правда, что есть меньше контроля, чем у вашего собственного NSOperationQueue. Но опять же, NSURLSession - это новый класс, целью которого, несомненно, является освобождение вас от этого бремени.
Нижняя строка: если вы хотите меньше хлопот - но меньше контроля - и доверять Apple, чтобы выполнять сетевые задачи грамотно от вашего имени, используйте NSURLSession. В противном случае сверните свой собственный с NSURLConnection и ваши собственные очереди операций.
Ответ 3
Обновление: Свойства executing
и finishing
сохраняют информацию о состоянии текущего NSOperation
. Если для параметра finishing
установлено значение YES
и executing
- NO
, ваша операция считается завершенной. Правильный способ борьбы с ним не требует dispatch_group
и может быть просто записан как асинхронный NSOperation
:
- (BOOL) isAsynchronous {
return YES;
}
- (void) main
{
// We are starting everything
self.executing = YES;
self.finished = NO;
NSURLSession * session = [NSURLSession sharedInstance];
NSURL *url = [NSURL URLWithString:@"http://someurl"];
NSURLSessionDataTask * dataTask = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
/* Do your stuff here */
NSLog("Will show in second");
self.executing = NO;
self.finished = YES;
}];
[dataTask resume]
}
Термин asynchronous
довольно вводит в заблуждение и не относится к дифференциации между потоком пользовательского интерфейса (основного) и фоновым потоком.
Если isAsynchronous
установлено в YES
, , это означает, что некоторая часть кода выполняется асинхронно в отношении метода main
. Сказано иначе: асинхронный вызов выполняется внутри метода main
, и метод завершится после завершения основного метода.
У меня есть некоторые слайды о том, как обращаться с concurrency на apple os: https://speakerdeck.com/yageek/concurrency-on-darwin.
Старый ответ. Вы можете попробовать dispatch_group_t
. Вы можете думать, что они сохраняют счетчик для GCD.
Представьте код ниже в методе main
вашего подкласса NSOperation
:
- (void) main
{
self.executing = YES;
self.finished = NO;
// Create a group -> value = 0
dispatch_group_t group = dispatch_group_create();
NSURLSession * session = [NSURLSession sharedInstance];
NSURL *url = [NSURL URLWithString:@"http://someurl"];
// Enter the group manually -> Value = Value + 1
dispatch_group_enter(group); ¨
NSURLSessionDataTask * dataTask = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
/* Do your stuff here */
NSLog("Will show in first");
//Leave the group manually -> Value = Value - 1
dispatch_group_leave(group);
}];
[dataTask resume];
// Wait for the group value to equals 0
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog("Will show in second");
self.executing = NO;
self.finished = YES;
}
Ответ 4
С NSURLSession вы не вручную добавляете какие-либо операции в очередь. Вы используете метод - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
в NSURLSession для создания задачи с данными, которую вы затем запускаете (вызывая метод возобновления).
Вы можете предоставить очередь операций, чтобы вы могли управлять свойствами очереди, а также использовать ее для других операций, если хотите.
Любые обычные действия, которые вы хотели бы предпринять в NSOperation (т.е. запуск, приостановка, остановка, возобновление), выполняемые в задаче данных.
Чтобы разместить 50 изображений для загрузки, вы можете просто создать 50 задач данных, которые NSURLSession будет правильно стоять в очереди.
Ответ 5
Если вы используете OperationQueue и не хотите, чтобы каждая операция создавала много одновременных сетевых запросов, вы можете просто вызвать queue.waitUntilAllOperationsAreFinished() после каждой операции, добавленной в очередь. Они будут выполняться только после завершения предыдущего, что значительно сократит количество одновременных сетевых подключений.
Ответ 6
Возможно, вы ищите это:
http://www.dribin.org/dave/blog/archives/2009/05/05/concurrent_operations/
Немного странно, что это не "встроено", но если вы хотите подключить NSURL-материал с помощью NSOperation, похоже, вам нужно повторно использовать runloop в основном потоке и сделать операцию "параллельной" ( "параллельно" в очереди).
Хотя в вашем случае - если речь идет о простой загрузке, без последующих зависимых операций, я не уверен, что вы выиграете с использованием NSOperation.