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

101

J'ai une méthode qui accepte un bloc et un bloc de complétion. Le premier bloc doit s'exécuter en arrière-plan, tandis que le bloc d'achèvement doit s'exécuter dans la file d'attente où la méthode a été appelée.

Pour ce dernier, j'ai toujours utilisé dispatch_get_current_queue(), mais il semble qu'il soit obsolète dans iOS 6 ou supérieur. Que dois-je utiliser à la place?

cfischer
la source
pourquoi dites-vous qu'il dispatch_get_current_queue()est obsolète dans iOS 6? la documentation n'en dit rien
jere
3
Le compilateur s'en plaint. Essayez-le.
cfischer
4
@jere Vérifiez le fichier d'en-tête, il indique qu'il est déprécié
WDUK
En plus des discussions sur les meilleures pratiques, je vois [NSOperationQueue currentQueue] qui peut répondre à la question. Je ne suis pas sûr des mises en garde quant à son utilisation.
Matt
mis en garde trouvé ------ [NSOperationQueue currentQueue] différent de dispatch_get_current_queue () ----- Il renvoie parfois null ---- dispatch_async (dispatch_get_global_queue (0, 0), ^ {NSLog (@ "q (0, 0) est% @ ", dispatch_get_current_queue ()); NSLog (@" cq (0,0) est% @ ", [NSOperationQueue currentQueue]);}); ----- q (0,0) est <OS_dispatch_queue_root: com.apple.root.default-qos [0x100195140]> cq (0,0) est (nul) ----- déprécié ou non dispatch_get_current_queue () semble être la seule solution que je vois pour signaler la file d'attente actuelle dans toutes les conditions
godzilla

Réponses:

64

Le modèle de "courir sur n'importe quelle 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 étranges.

Mon approche préférée à ce sujet est de dire "le bloc de complétion s'exécute sur une file d'attente définie par l'implémentation avec ces propriétés: x, y, z", et de laisser le bloc se répartir vers une file d'attente particulière si l'appelant veut 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 par l'application".

** ÉDITER **

Catfish_Man a mis un exemple dans les commentaires ci-dessous, je l'ajoute simplement à sa réponse.

- (void) aMethodWithCompletionBlock:(dispatch_block_t)completionHandler     
{ 
    dispatch_async(self.workQueue, ^{ 
        [self doSomeWork]; 
        dispatch_async(self.callbackQueue, completionHandler); 
    } 
}
Catfish_Man
la source
7
Tout à fait d'accord. Vous pouvez voir qu'Apple suit toujours ceci; chaque fois que vous voulez faire quelque chose sur la file d'attente principale, vous devez toujours envoyer à la file d'attente principale car Apple garantit toujours que vous êtes sur un thread différent. La plupart du temps, vous attendez un long processus pour terminer la récupération / la manipulation des données, puis vous pouvez les traiter en arrière-plan directement dans votre bloc de saisie semi-automatique, puis ne coller que les appels d'interface utilisateur dans un bloc de répartition de la file d'attente principale. De plus, il est toujours bon de suivre ce qu'Apple définit en termes d'attentes, car les développeurs seront habitués au modèle.
Jack Lawrence
1
excellente réponse ... mais j'espérais au moins un exemple de code pour illustrer ce que vous dites
abbood
3
- (void) aMethodWithCompletionBlock: (dispatch_block_t) completionHandler {dispatch_async (self.workQueue, ^ {[self doSomeWork]; dispatch_async (self.callbackQueue, completionHandler);}}
Catfish_Man
(Pour un exemple complètement trivial)
Catfish_Man
3
Ce n'est pas possible dans le cas général car il est possible (et en fait assez probable) d'être sur plus d'une file simultanément, à cause de dispatch_sync () et dispatch_set_target_queue (). Certains sous-ensembles du cas général sont possibles.
Catfish_Man
27

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

  1. Le "blocage à exécuter" doit être exécuté sur une file d'attente interne, par exemple une file d'attente privée pour l'API et donc entièrement sous le contrôle de cette API. La seule exception à cela 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 simultanées globales.

  2. Le bloc d'achèvement doit toujours être exprimé comme un tuple (file d'attente, bloc) à moins que les mêmes hypothèses que pour # 1 ne soient vraies, par exemple le bloc d'achèvement sera exécuté sur une file d'attente globale connue. Le bloc d'achèvement doit en outre être distribué de manière asynchrone sur la file d'attente transmise.

Ce ne sont pas seulement des points stylistiques, ils sont entièrement nécessaires si votre API doit être à l'abri des blocages ou de tout autre comportement de cas de bord qui vous accrocherait un jour à l'arbre le plus proche. :-)

jkh
la source
11
Cela semble raisonnable, mais pour une raison quelconque, ce n'est pas l'approche adoptée par Apple pour ses propres API: la plupart des méthodes qui prennent un bloc d'achèvement ne prennent pas également une file d'attente ...
cfischer
2
C'est vrai, et pour modifier quelque peu mon assertion précédente, s'il est manifestement évident que le bloc d'achèvement sera exécuté sur la file d'attente principale ou une file d'attente simultanée globale. Je changerai ma réponse pour l'indiquer.
jkh
Pour dire qu'Apple n'adopte pas cette approche: Apple n'a pas toujours «raison» par définition. Les arguments appropriés sont toujours plus forts que toute autorité particulière, ce que tout scientifique confirmera. Je pense que la réponse ci-dessus l'indique très bien du point de vue de l'architecture logicielle appropriée.
Werner Altewischer
14

Les autres réponses sont excellentes, mais pour moi, la réponse est structurelle. J'ai une méthode comme celle-ci 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 les répartir sur l'autre thread.

Dan Rosenstark
la source
12

Vous devez dispatch_get_current_queueen premier lieu faire attention à votre utilisation . Depuis le fichier d'en-tête:

Recommandé à des fins de débogage et de journalisation uniquement:

Le code ne doit faire aucune hypothèse sur la file d'attente renvoyée, sauf s'il s'agit d'une des files d'attente globales ou d'une file d'attente que le code a lui-même créée. Le code ne doit pas supposer que l'exécution synchrone sur une file d'attente est à l'abri d'un blocage 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 publié (si vous l'avez créée via dispatch_queue_create) et utilisez-la à partir de là.

  2. Utilisez les files d'attente définies par le système via dispatch_get_global_queueet gardez une trace de celle que vous utilisez.

En effet, tout en comptant auparavant sur le système pour suivre la file d'attente dans laquelle vous vous trouvez, vous devrez le faire vous-même.

WDUK
la source
16
Comment pouvons-nous "conserver une référence à la file d'attente sur laquelle vous avez initialement posté" si nous ne pouvons pas utiliser dispatch_get_current_queue()pour savoir de quelle file d'attente il s'agit? Parfois, le code qui a besoin de savoir sur quelle file d'attente il s'exécute n'a aucun contrôle ou aucune connaissance de celui-ci. J'ai beaucoup de code qui peut (et devrait) être exécuté sur une file d'attente en arrière-plan, mais j'ai parfois besoin de mettre à jour l'interface graphique (barre de progression, etc.), et j'ai donc besoin de dispatch_sync () vers la file d'attente principale pour ces opérations. Si déjà dans la file d'attente principale, dispatch_sync () se verrouille pour toujours. Cela me prendra des mois pour refactoriser mon code pour cela.
Abhi Beckert
3
Je pense que NSURLConnection donne ses rappels d'achèvement sur le même thread à partir duquel il est appelé. Serait-il en utilisant la même API "dispatch_get_current_queue" pour stocker la file d'attente à partir de laquelle elle est appelée pour être utilisée au moment du rappel?
defactodeity
5

Apple était obsolète dispatch_get_current_queue(), mais a laissé un trou à un autre endroit, nous sommes donc toujours en mesure d'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 underlyingQueuepropriété est disponible depuis iOS 8.

Si vous devez effectuer le bloc de complétion dans la file d'attente d'origine, vous pouvez également utiliser OperationQueuedirectement, je veux dire sans GCD.

Kelin
la source
4

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

alexey.hippie
la source
0

C'est une réponse moi aussi. Je vais donc parler de notre cas d'utilisation.

Nous avons une couche de services et la couche d'interface utilisateur (entre autres couches). La couche de services exécute les tâches en arrière-plan. (Tâches de manipulation de données, tâches CoreData, appels réseau, etc.). La couche de service a quelques files d'attente d'opérations pour satisfaire les 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 de réussite. Ce bloc peut contenir du code UIKit. Un cas d'utilisation simple consiste à récupérer tous les messages du serveur et à recharger la vue de collection.

Ici, nous garantissons que les blocs passés dans la couche services sont distribués dans la file d'attente sur laquelle le service a été appelé. Puisque dispatch_get_current_queue est une méthode obsolète, 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 depuis l'extérieur du contexte d'une opération en cours entraîne généralement le renvoi de nil.

Puisque nous invoquons toujours nos services sur une file d'attente connue (nos files d'attente personnalisées et la file d'attente principale), cela fonctionne bien pour nous. Nous avons des cas où serviceA peut appeler serviceB qui peut appeler serviceC. Puisque nous contrôlons la provenance du premier appel de service, nous savons que le reste des services suivra les mêmes règles.

Ainsi, NSOperationQueue.currentQueue retournera toujours l'une de nos files d'attente ou la MainQueue.

Kris Subramanian
la source