web-dev-qa-db-fra.com

Alternatives à dispatch_get_current_queue () pour les blocs de complétion sous iOS 6?

J'ai une méthode qui accepte un bloc et un bloc d'achèvement. Le premier bloc doit être exécuté en arrière-plan, tandis que le bloc d'achèvement doit être exécuté dans la file d'attente de la méthode.

Pour ce dernier, j'ai toujours utilisé dispatch_get_current_queue(), mais il semble que cela soit déconseillé sous iOS 6 ou supérieur. Que devrais-je utiliser à la place?

98
cfischer

Le motif "exécuter sur la file d'attente de l'appelant" est attrayant, mais finalement pas une bonne idée. Cette file d'attente peut être une file d'attente de faible priorité, la file d'attente principale ou une autre file d'attente avec des propriétés impaires.

Mon approche préférée consiste à dire "le bloc d'achèvement s'exécute sur une file d'attente définie par l'implémentation avec les propriétés suivantes: x, y, z", et laisse le bloc être envoyé à une file d'attente particulière si l'appelant souhaite plus de contrôle que cela. Un ensemble typique de propriétés à spécifier serait quelque chose comme "série, non réentrant et asynchrone par rapport à toute autre file d'attente visible de l'application".

** MODIFIER **

Catfish_Man a donné un exemple dans les commentaires ci-dessous, je ne fais que l'ajouter à sa réponse.

- (void) aMethodWithCompletionBlock:(dispatch_block_t)completionHandler     
{ 
    dispatch_async(self.workQueue, ^{ 
        [self doSomeWork]; 
        dispatch_async(self.callbackQueue, completionHandler); 
    } 
}
63
Catfish_Man

C'est fondamentalement la mauvaise approche à adopter pour l'API que vous décrivez. Si une API accepte l'exécution d'un bloc et d'un bloc d'achèvement, les faits suivants doivent être vérifiés:

  1. Le "bloc à exécuter" doit être exécuté sur une file d'attente interne, par exemple. une file d'attente privée de l'API et donc entièrement sous le contrôle de cette API. La seule exception à cette règle est si l'API déclare spécifiquement que le bloc sera exécuté sur la file d'attente principale ou sur l'une des files d'attente globales simultanées.

  2. Le bloc d'achèvement doit toujours être exprimé sous forme de tuple (file d'attente, bloc), à moins que les mêmes hypothèses que pour # 1 soient vraies, par exemple. le bloc d'achèvement sera exécuté sur une file d'attente globale connue. En outre, le bloc d'achèvement doit être envoyé en mode asynchrone sur la file d'attente transférée.

Ce ne sont pas seulement des points stylistiques, ils sont tout à fait nécessaires si votre API doit être protégée des impasses ou de tout autre comportement de cas Edge qui, autrement, vous accrocheront un jour à l'arbre le plus proche. :-)

24
jkh

Les autres réponses sont excellentes, mais pour moi la réponse est structurelle. J'ai une méthode comme celle-ci qui est sur un Singleton:

- (void) dispatchOnHighPriorityNonMainQueue:(simplest_block)block forceAsync:(BOOL)forceAsync {
    if (forceAsync || [NSThread isMainThread])
        dispatch_async_on_high_priority_queue(block);
    else
        block();
}

qui a deux dépendances, qui sont:

static void dispatch_async_on_high_priority_queue(dispatch_block_t block) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), block);
}

et

typedef void (^simplest_block)(void); // also could use dispatch_block_t

De cette façon, je centralise mes appels pour envoyer sur l'autre thread.

14
Dan Rosenstark

Vous devez faire attention à votre utilisation de dispatch_get_current_queue en premier lieu. À partir du fichier d'en-tête:

Recommandé pour le débogage et la journalisation uniquement:

Le code ne doit faire aucune hypothèse sur la file d'attente renvoyée, à moins qu'il ne s'agisse d'une file d'attente globale ou d'une file d'attente créée par le code lui-même. Le code ne doit pas supposer que l'exécution synchrone sur une file d'attente ne présente aucun risque d'interblocage si cette file d'attente n'est pas celle renvoyée par dispatch_get_current_queue ().

Vous pouvez faire l'une des deux choses suivantes:

  1. Conservez une référence à la file d'attente sur laquelle vous avez initialement posté (si vous l'avez créée via dispatch_queue_create), et l'utiliser à partir de là.

  2. Utiliser les files d'attente définies par le système via dispatch_get_global_queue, et gardez une trace de celui que vous utilisez.

Effectivement, alors que vous vous fiez auparavant au système pour garder une trace de la file d'attente, vous devrez le faire vous-même.

12
WDUK

Pour ceux qui ont encore besoin de comparer les files d'attente, vous pouvez comparer les files d'attente en fonction de leur libellé ou de leur spécification. Vérifiez ceci https://stackoverflow.com/a/23220741/1531141

4
alexey.hippie

Apple avait déprécié dispatch_get_current_queue(), mais avait laissé un trou à un autre endroit. Nous avons donc toujours réussi à obtenir la file d'attente de distribution actuelle:

if let currentDispatch = OperationQueue.current?.underlyingQueue {
    print(currentDispatch)
    // Do stuff
}

Cela fonctionne au moins pour la file d'attente principale. Notez que cette propriété underlyingQueue est disponible depuis iOS 8.

Si vous devez exécuter le bloc d'achèvement dans la file d'attente d'origine, vous pouvez également utiliser OperationQueue directement, je veux dire sans GCD.

4
kelin

Ceci est un moi aussi répondre. Je vais donc parler de notre cas d'utilisation.

Nous avons une couche de services et la couche d’interface utilisateur (parmi d’autres couches). La couche de services exécute des tâches en arrière-plan. (Tâches de manipulation de données, tâches CoreData, appels réseau, etc.). La couche service comporte plusieurs files d'attente d'opérations pour répondre aux besoins de la couche d'interface utilisateur.

La couche d'interface utilisateur s'appuie sur la couche de services pour effectuer son travail, puis exécuter un bloc d'achèvement de réussite. Ce bloc peut contenir du code UIKit. Un cas d'utilisation simple consiste à extraire tous les messages du serveur et à recharger la vue de collecte.

Ici, nous garantissons que les blocs qui sont passés dans la couche services sont distribués dans la file d'attente sur laquelle le service a été appelé. Dispatch_get_current_queue étant une méthode déconseillée, nous utilisons NSOperationQueue.currentQueue pour obtenir la file d'attente actuelle de l'appelant. Note importante sur cette propriété.

L'appel de cette méthode en dehors du contexte d'une opération en cours entraîne généralement le renvoi de zéro.

Comme nous appelons toujours nos services sur une file d'attente connue (nos files d'attente personnalisées et notre file d'attente principale), cela fonctionne bien pour nous. Nous avons des cas où serviceA peut appeler serviceB qui peut appeler serviceC. Étant donné que nous contrôlons l'origine du premier appel de service, nous savons que les autres services suivront les mêmes règles.

Donc, NSOperationQueue.currentQueue renverra toujours l’une de nos files d’attente ou la MainQueue.

0
Kris Subramanian