web-dev-qa-db-fra.com

Comment utiliser NSOperationQueue avec NSURLSession?

J'essaie de créer un téléchargeur d'images en vrac, où les images peuvent être ajoutées à une file d'attente à la volée pour être téléchargées, et je peux découvrir la progression et quand elles ont fini de télécharger.

À travers ma lecture, il semble que NSOperationQueue pour la fonctionnalité de file d'attente et NSURLSession pour la fonctionnalité de réseau semble être mon meilleur pari, mais je ne sais pas comment utiliser les deux en tandem.

Je sais que j'ajoute des instances de NSOperation au NSOperationQueue et elles sont mises en file d'attente. Et il semble que je crée une tâche de téléchargement avec NSURLSessionDownloadTask, et plusieurs si j'ai besoin de plusieurs tâches, mais je ne sais pas comment je les assemble.

NSURLSessionDownloadTaskDelegate semble avoir toutes les informations dont j'ai besoin pour les notifications de progression et d'achèvement du téléchargement, mais je dois également être en mesure d'arrêter un téléchargement spécifique, d'arrêter tous les téléchargements et de gérer les données que je récupère du téléchargement .

53
Doug Smith

Votre intuition ici est correcte. Si vous émettez plusieurs demandes, avoir un NSOperationQueue avec maxConcurrentOperationCount de 4 ou 5 peut être très utile. En l'absence de cela, si vous émettez de nombreuses demandes (disons, 50 grandes images), vous pouvez rencontrer des problèmes de dépassement de délai lorsque vous travaillez sur une connexion réseau lente (par exemple, certaines connexions cellulaires). Les files d'attente d'opérations présentent également d'autres avantages (par exemple, les dépendances, l'attribution de priorités, etc.), mais le contrôle du degré de concurrence est le principal avantage, à mon humble avis.

Si vous utilisez des requêtes basées sur completionHandler, l'implémentation d'une solution basée sur les opérations est assez banale (il s'agit de l'implémentation de sous-classe NSOperation simultanée typique; voir le Configuration des opérations pour simultané Section Exécution du Operation Queues chapitre du Guide de programmation de la concurrence pour plus d'informations) .

Si vous utilisez l'implémentation basée sur delegate, les choses commencent à devenir assez velues assez rapidement. Cela est dû à une fonctionnalité compréhensible (mais incroyablement ennuyeuse) de NSURLSession par laquelle les délégués au niveau de la tâche sont implémentés au niveau de la session. (Pensez à cela: deux requêtes différentes qui nécessitent une gestion différente appellent la même méthode déléguée sur l'objet de session partagée. Egad!)

L'encapsulation d'un NSURLSessionTask basé sur un délégué dans une opération peut être effectuée (moi et d'autres, j'en suis sûr, je l'ai fait), mais cela implique un processus compliqué pour que l'objet de session maintienne un dictionnaire de références croisées identifiant les tâches avec les objets d'opération de tâche, faites-lui passer ces méthodes de délégué de tâche passées à l'objet de tâche, puis assurez-vous que les objets de tâche sont conformes aux divers protocoles de délégué NSURLSessionTask. C'est une quantité de travail assez importante car NSURLSession ne fournit pas de fonctionnalité de style maxConcurrentOperationCount sur la session (pour ne rien dire des autres NSOperationQueue bonté, comme les dépendances, l'achèvement blocs, etc.).

Et il convient de souligner que l'implémentation basée sur les opérations n'est pas un démarreur avec des sessions d'arrière-plan, cependant. Vos tâches de téléchargement/téléchargement continueront de fonctionner bien après la fin de l'application (ce qui est une bonne chose, c'est un comportement assez essentiel dans une demande en arrière-plan), mais lorsque votre application est redémarrée, la file d'attente des opérations et toutes ses opérations ont disparu . Vous devez donc utiliser une implémentation NSURLSession purement déléguée pour les sessions en arrière-plan.

46
Rob

Conceptuellement, NSURLSession est une file d'attente d'opérations. Si vous reprenez une tâche NSURLSession et un point d'arrêt sur le gestionnaire d'achèvement, la trace de la pile peut être assez révélatrice.

Voici un extrait du tutoriel toujours fidèle de Ray Wenderlich sur NSURLSession avec une instruction NSLog ajoutée au point d'arrêt lors de l'exécution du gestionnaire d'achèvement:

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

Ci-dessus, nous pouvons voir le gestionnaire d'achèvement exécuté dans Thread 5 Queue: NSOperationQueue Serial Queue.

Donc, je suppose que chaque NSURLSession maintient sa propre file d'attente d'opérations, et chaque tâche ajoutée à une session est - sous le capot - exécutée en tant que NSOperation. Par conséquent, il n'est pas logique de conserver une file d'attente d'opérations qui contrôle les objets NSURLSession ou les tâches NSURLSession.

NSURLSessionTask lui-même propose déjà des méthodes équivalentes telles que cancel, resume, suspend, etc.

Il est vrai qu'il y a moins de contrôle que vous n'auriez avec votre propre NSOperationQueue. Mais là encore, NSURLSession est une nouvelle classe dont le but est sans aucun doute de vous soulager de ce fardeau.

Conclusion: si vous voulez moins de tracas - mais moins de contrôle - et faites confiance à Apple pour effectuer les tâches réseau avec compétence en votre nom, utilisez NSURLSession. Sinon, lancez le vôtre avec NSURLConnection et vos propres files d'attente d'opérations.

38
Max MacLeod

Mise à jour: Les propriétés executing et finishing contiennent la connaissance de l'état de l'actuel NSOperation . Une fois que finishing est défini sur YES et executing sur NO, votre opération est considérée comme terminée. La manière correcte de le traiter ne nécessite pas de dispatch_group et peut simplement être écrit comme un asynchrone 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]
   }

Le terme asynchronous est assez trompeur et ne fait pas référence à la différenciation entre le thread d'interface utilisateur (principal) et le thread d'arrière-plan.

Si isAsynchronous est défini sur YES, cela signifie qu'une partie du code est exécutée de manière asynchrone concernant la méthode main. Autrement dit: un appel asynchrone est effectué à l'intérieur de la méthode main et la méthode se terminera après la fin de la méthode principale .

J'ai quelques diapositives sur la façon de gérer la concurrence sur Apple os: https://speakerdeck.com/yageek/concurrency-on-darwin .

Ancienne réponse : Vous pouvez essayer le dispatch_group_t. Vous pouvez les considérer comme des compteurs conservés pour GCD.

Imaginez le code ci-dessous dans la méthode main de votre sous-classe 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;
}
5
yageek

Avec NSURLSession, vous n'ajoutez manuellement aucune opération à une file d'attente. Vous utilisez la méthode - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request sur NSURLSession pour générer une tâche de données que vous démarrez ensuite (en appelant la méthode resume).

Vous êtes autorisé à fournir la file d'attente des opérations afin de pouvoir contrôler les propriétés de la file d'attente et également l'utiliser pour d'autres opérations si vous le souhaitez.

N'importe laquelle des actions habituelles que vous voudriez entreprendre sur une opération NSO (c'est-à-dire démarrer, suspendre, arrêter, reprendre) que vous effectuez sur la tâche de données.

Pour mettre en file d'attente 50 images à télécharger, vous pouvez simplement créer 50 tâches de données que NSURLSession mettra correctement en file d'attente.

2
Reid Main

Si vous utilisez OperationQueue et que vous ne voulez pas que chaque opération crée de nombreuses demandes réseau simultanées, vous pouvez simplement appeler queue.waitUntilAllOperationsAreFinished () après chaque opération est ajoutée à la file d'attente. Ils ne s'exécuteront désormais qu'une fois la précédente terminée, ce qui réduira considérablement le nombre de connexions réseau simultanées.

0
Above The Gods