¿Cómo uso NSOperationQueue con NSURLSession?


Estoy tratando de construir un descargador de imágenes masivo, donde las imágenes se pueden agregar a una cola sobre la marcha para ser descargadas, y puedo averiguar el progreso y cuándo han terminado de descargarse.

A través de mi lectura parece que NSOperationQueue para la funcionalidad de cola y NSURLSession para la funcionalidad de red parece mi mejor apuesta, pero estoy confundido en cuanto a cómo usar los dos en tándem.

Sé que agrego instancias de NSOperation a la NSOperationQueue y se ponen en cola. Y parece que creo una tarea de descarga con NSURLSessionDownloadTask, y múltiple si necesito múltiples tareas, pero no estoy seguro de cómo juntar las dos.

NSURLSessionDownloadTaskDelegate parece tener toda la información que necesito para el progreso de la descarga y las notificaciones de finalización, pero también necesito poder detener una descarga específica, detener todas las descargas y lidiar con los datos que obtengo de la descarga.

Author: Doug Smith, 2014-02-21

6 answers

Su intuición aquí es correcta. Si se emiten muchas solicitudes, tener un NSOperationQueue con maxConcurrentOperationCount de 4 o 5 puede ser muy útil. En ausencia de eso, si emite muchas solicitudes (digamos, 50 imágenes grandes), puede sufrir problemas de tiempo de espera cuando trabaja con una conexión de red lenta (por ejemplo, algunas conexiones celulares). Las colas de operación también tienen otras ventajas (por ejemplo, dependencias, asignación de prioridades, etc.).), pero controlar el grado de concurrencia es el beneficio clave, IMHO.

Si está utilizando completionHandler solicitudes basadas, la implementación de la solución basada en operaciones es bastante trivial (es la típica implementación de subclase concurrente NSOperation; consulte la sección Configuración de operaciones para ejecución Concurrente del capítulo Colas de operaciones de la Guía de Programación de Concurrencia para obtener más información).

Si está utilizando la implementación basada en delegate, las cosas comienzan a ponerse bastante complicadas bastante rápido, sin embargo. Esto se debe a una comprensible (pero increíblemente molesto) característica de NSURLSession mediante la cual los delegados a nivel de tarea se implementan a nivel de sesión. (Piense en eso: Dos solicitudes diferentes que requieren un manejo diferente están llamando al mismo método delegado en el objeto de sesión compartido. ¡Egad!)

Envolver un NSURLSessionTask basado en delegados en una operación se puede hacer (yo, y otros estoy seguro, lo han hecho), pero implica un proceso difícil de hacer que el objeto de sesión mantenga un diccionario de referencias cruzadas identificadores de tareas con objetos de operación de tarea, haga que pase estos métodos de delegado de tarea pasados al objeto de tarea, y luego haga que los objetos de tarea se ajusten a los diversos protocolos de delegado NSURLSessionTask. Es una cantidad bastante significativa de trabajo requerido porque NSURLSession no proporciona una característica de estilo maxConcurrentOperationCount en la sesión (por no hablar de otras bondades NSOperationQueue, como dependencias, bloques de finalización, etc.).).

Y vale la pena señalar que la implementación basada en operaciones es un poco no iniciadora con antecedentes sesiones, sin embargo. Sus tareas de carga/descarga continuarán funcionando bien después de que la aplicación se haya terminado (lo cual es bueno, es un comportamiento bastante esencial en una solicitud en segundo plano), pero cuando se reinicia su aplicación, la cola de operaciones y todas sus operaciones desaparecen. Por lo tanto, debe usar una implementación pura basada en delegados NSURLSession para las sesiones en segundo plano.

 40
Author: Rob,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-23 22:00:33

Conceptualmente, NSURLSession es una cola de operaciones. Si reanuda una tarea de NSURLSession y un punto de interrupción en el controlador de finalización, el seguimiento de pila puede ser bastante revelador.

Aquí hay un extracto del siempre fiel tutorial de Ray Wenderlich sobre NSURLSession con una instrucción NSLog añadida al punto de interrupción al ejecutar el controlador de finalización:

NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:[NSURL URLWithString:londonWeatherUrl]
          completionHandler:^(NSData *data,
                              NSURLResponse *response,
                              NSError *error) {
            // handle response
            NSLog(@"Handle response"); // <-- breakpoint here       

  }] resume];

Punto de interrupción de la cola serie de NSOperationQueue

Arriba, podemos ver el controlador de finalización siendo ejecutado en Thread 5 Queue: NSOperationQueue Serial Queue.

Entonces, mi conjetura es que cada NSURLSession mantiene su propia cola de operaciones, y cada tarea añadida a una sesión se ejecuta, bajo el capó, como una NSOperation. Por lo tanto, no tiene sentido mantener una cola de operaciones que controle los objetos NSURLSession o las tareas NSURLSession.

NSURLSessionTask ya ofrece métodos equivalentes como cancel, resume, suspend, y así sucesivamente.

Es cierto que hay menos control del que tendría con su propia NSOperationQueue. Pero, de nuevo, NSURLSession es un nueva clase cuyo propósito es indudablemente aliviaros de esa carga.

En pocas palabras: si desea menos complicaciones, pero menos control, y confía en Apple para realizar las tareas de red de manera competente en su nombre, use NSURLSession. De lo contrario, haga su propio rollo con NSURLConnection y sus propias colas de operación.

 38
Author: Max MacLeod,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-25 16:42:45

Actualización: Las propiedades executing y finishing contienen el conocimiento sobre el estado del NSOperation actual. Una vez que finishing se establece en YES y executing a NO, su operación se considera terminada. La forma correcta de tratar con él no requiere un dispatch_group y simplemente se puede escribir como un NSOperation asíncrono:

  - (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]
   }

El término asynchronous es bastante engañoso y no se refiere a la diferenciación entre el hilo de interfaz de usuario (principal) y el hilo de fondo.

Si isAsynchronous está establecido to YES, significa que alguna parte del código se ejecuta asíncronamente con respecto al método main . Dicho de otra manera: se realiza una llamada asíncrona dentro del método main y el método terminará después de que el método principal termine.

Tengo algunas diapositivas sobre cómo manejar la concurrencia en el sistema operativo apple: https://speakerdeck.com/yageek/concurrency-on-darwin .

Respuesta antigua : Puedes probar el dispatch_group_t. Usted puede pensar que como retener contador para GCD.

Imagine el siguiente código en el método main de su subclase 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's value to equals 0
  dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

  NSLog("Will show in second");

  self.executing = NO;
  self.finished = YES;
}
 6
Author: yageek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-09-06 14:15:57

Con NSURLSession no agrega manualmente ninguna operación a una cola. Utiliza el método - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request en NSURLSession para generar una tarea de datos que luego inicia (llamando al método resume).

Se le permite proporcionar la cola de operaciones para que pueda controlar las propiedades de la cola y también usarla para otras operaciones si lo desea.

Cualquiera de las acciones habituales que desea realizar en una NSOperation (es decir, iniciar, pausar, detener, reanudar) que realiza en los datos tarea.

Para poner en cola 50 imágenes para descargar, simplemente puede crear 50 tareas de datos que NSURLSession pondrá en cola correctamente.

 2
Author: Reid Main,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-20 21:49:04

Si está utilizando OperationQueue y no desea que cada operación cree muchas solicitudes de red simultáneas, simplemente puede llamar a la cola.waitUntilAllOperationsAreFinished () después de agregar cada operación a la cola. Ahora solo se ejecutarán después de que se complete el anterior, reduciendo significativamente la cantidad de conexiones de red simultáneas.

 0
Author: Above The Gods,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-09 12:43:39

Tal vez usted está buscando esto:

Http://www.dribin.org/dave/blog/archives/2009/05/05/concurrent_operations /

Es un poco raro que esto no sea 'builtin', pero si quieres conectar cosas de NSURL con NSOperation parece que tienes que reutilizar el runloop en el hilo principal y hacer que la operación sea 'concurrente' ('concurrente' a la cola).

Aunque en su caso , si se trata de descargas simples, sin operaciones dependientes posteriores conectado-No estoy seguro de lo que ganaría con el uso de NSOperation.

 -1
Author: hnh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-26 22:44:02