6

J'ai regardé certaines des présentations de la WWDC 2010 et j'ai également lu la plupart des documents sur les blocs et la simultanéité et j'ai quelques questions concernant l'utilisation de blocs avec des files d'attente série dans Grand Central Dispatch. J'ai un projet iOS 4 qui a une scrollview et un dictionnaire d'informations d'image - urls aux images et ainsi de suite. Je veux utiliser GCD et les blocs pour télécharger les images et les mettre dans mon scrollview ne bloquant pas le fil principal. J'ai écrit le code suivant qui semble fonctionner:iOS 4 questions GCD

for (NSDictionary* dict in images) 
{ 
    dispatch_async(image_queue, ^{ 

      NSString* urlString = [dict objectForKey:@"url"]; 
      NSURL* url = [NSURL URLWithString:urlString]; 
      NSData* imageData = [[NSData alloc] initWithContentsOfURL:url]; 
      UIImage* image = [UIImage imageWithData:imageData]; 
      UIImageView* imageView = // initialize imageView with image;  

      dispatch_async(dispatch_get_main_queue(), ^{ 
       [self.scrollView addSubview:imageView]; 
      }); 
      [imageData release]; 
     }); 
} 

J'ai deux questions:

  1. Selon le guide d'accès concurrentiel je ne devrais pas saisir les variables de la portée englobante qui sont des types non scalaires - Dans mon code, je capture dict qui est un objet NSDictionary *. Si je ne suis pas autorisé à le capturer, comment puis-je écrire le code? Est-ce qu'un bloc capture uniquement les variables de la portée englobante qui sont réellement utilisées? Que se passe-t-il si je quitte le ViewController actuel avant que toutes les images ne soient récupérées dans la file d'attente d'expédition en série? Je ne pense pas qu'ils sont conscients que le ViewController qui les a créés est parti alors que se passe-t-il lorsqu'ils exécutent le gestionnaire de complétion où j'insère les vues de l'image dans mon scrollview sur le thread principal? Cela provoque-t-il une erreur ou quoi? Et comment puis-je annuler toutes les opérations restantes sur la file d'attente série lorsque mon ViewController disparaît?

Meilleures salutations,

+0

J'aimerais également voir quelques bonnes pratiques. –

Répondre

10
  1. Bien qu'il soit un point tatillonne, ce qui est important pour comprendre ce que le guide tente de concurrency vous dire: Pointeurs sont types scalaires. Ainsi, vous pouvez capturer des pointeurs à l'intérieur des blocs tout ce que vous voulez ... MAIS vous devez être conscient de la durée de vie de la mémoire vers laquelle ils pointent! NSDictionary * est un id de type, et lorsque vous référencez un identifiant dans un bloc, le runtime prend la responsabilité de conserver l'identifiant si le bloc est copié (par dispatch_async()) puis de le libérer lorsque le bloc lui-même est désaffecté. Et oui, un bloc ne capture que les variables qui y sont référencées. Puisque vous savez maintenant que le bloc asynchrone a fait une retenue sur soi, il devrait être clair que les erreurs de gestion de la mémoire (modulo) que votre ViewController ne peut pas "disparaître" jusqu'à ce que le bloc soit terminé. Donc, ça ne va pas tomber en panne - mais vous avez raison de noter que vous voulez vraiment un moyen d'annuler ce genre de travail asynchrone lorsque vous ne prévoyez pas d'utiliser les résultats plus. Un modèle simple mais efficace consiste à mettre un test au début de votre bloc asynchrone qui vérifie si le travail doit encore être fait.

1

@Kaelin Colclasure: pour la 1ère question, il semble plus probable un problème d'état partagé en application multithread: pour le type intégral vous avez une copie par valeur (qui applique aux pointeurs aussi), mais lorsque vous utiliserez un objet référencé par un pointeur vous aurez tous les problèmes liés à l'absence de verrouillage (type de variation sur le problème de durée de vie de l'objet que vous avez mentionné ici).

0

est ici la différence pratique pour votre premier point:

Si je passe un scalaire dans un bloc utilisé dans une file d'attente de GCD, puis à l'intérieur du bloc, je travaille avec une copie des données d'origine. Les modifications ne seront pas visibles en dehors du bloc. (Il y a des mises en garde à cela, le modificateur __block par exemple mais en général c'est correct.)

Si je passe, disons, un NSMutableDictionary dans un bloc, les modifications du dictionnaire sera visible en dehors du bloc - c'est parce que vous avez retenu un dans le dictionnaire de référence et n'a pas pris une copie en profondeur . Dans les deux cas, la gestion de la mémoire est effectuée pour vous, c'est-à-dire que la variable scalaire est copiée ou les objets sont conservés.

Puisque vous ne pouvez pas modifier le contenu d'un NSDictionary après son initialisation, vous trouverez probablement que les blocs effectuent automatiquement la bonne opération pour vous. Pour le second point, la gestion de la mémoire est quasiment automatique sauf si vous devez travailler sur une copie d'un objet mutable.

3

Depuis que d'autres ont répondu à vos deux questions, je voudrais commenter votre code et vous recommandons de ne pas utiliser GCD pour les requêtes réseau comme les images. Le principal problème avec eux est qu'ils vont tous fonctionner simultanément. Selon le nombre de téléchargements, vous risquez de créer trop de connexions simultanées qui pourraient ralentir une connexion cellulaire, et l'utilisateur pourrait penser que quelque chose ne va pas si les images ne commencent pas à apparaître pendant qu'elles se battent pour le précieux réseau . Essayez d'utiliser un NSOperationQueue avec une valeur de maxConcurrentOperationCount de 2 ou 3. Cela vous permettra de mettre en file d'attente une quantité potentiellement infinie de demandes de réseau, mais au plus quelques-unes fonctionneront en parallèle. Depuis que vous pouvez accéder à l'état du réseau de l'appareil, vous pouvez l'augmenter conditionnellement à 8 pour les connexions wifi.

Et le deuxième problème avec GCD, c'est qu'il est un peu lourd d'annuler les opérations en attente. Si votre utilisateur entre dans un contrôleur de vue puis repousse, en fonction de la programmation de votre code, les blocs GCD conservent le contrôleur de vue, l'empêchent d'être libéré et en fait toutes les opérations du réseau doivent se terminer jusqu'à ce que le contrôleur libéré (donc pas de point dans l'annulation des connexions dans dealloc).

J'ai appris cela à la dure en surveillant un proxy et en passant rapidement dans une vue et retour. Il était vraiment effroyable de voir comment les connexions en attente infligées environ 20 secondes de bande passante réseau supplémentaire endommagent à mon application autrement ignorant. Tl; dr utiliser une file d'attente pour le réseau, GCD pour le traitement d'image/mise à l'échelle/mise à jour de l'interface graphique.

+0

Les blocs GCD ne s'exécuteront que s'ils sont envoyés dans une file d'attente de répartition globale. Si vous créez une file d'attente de répartition en série (une file d'attente de répartition privée), les tâches sont exécutées de manière séquentielle. Voir [Guide de programmation de concurrence d'Apple] (http://developer.apple.com/library/ios/#documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html). –

+0

Bien sûr, le problème est que vous ne savez pas ce que 'image_queue' est, il aurait pu être assigné à' dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_LOW, 0) '. –

+0

@Ducan Il semble que le problème avec l'utilisation d'une file d'attente série est que les choses sont totalement séquentielles au lieu d'avoir la concurrence contrainte que maxConcurrentOperationCount autorise comme dit Grzegorz. – Lee