Как использовать 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.