Alternative a dispatch_get_current_queue () per i blocchi di completamento in iOS 6?


101

Ho un metodo che accetta un blocco e un blocco di completamento. Il primo blocco dovrebbe essere eseguito in background, mentre il blocco di completamento dovrebbe essere eseguito in qualsiasi coda sia stato chiamato il metodo.

Per quest'ultimo l'ho sempre usato dispatch_get_current_queue(), ma sembra che sia deprecato in iOS 6 o versioni successive. Cosa dovrei usare invece?


perché dici che dispatch_get_current_queue()è deprecato in iOS 6? i documenti non dicono nulla al riguardo
jere

3
Il compilatore se ne lamenta. Provalo.
cfischer

4
@jere Controlla il file di intestazione, afferma che è
deprecato

A parte le discussioni su ciò che è la migliore pratica, vedo [NSOperationQueue currentQueue] che potrebbe rispondere alla domanda. Non sono sicuro degli avvertimenti sul suo utilizzo.
Matt

avvertenza trovata ------ [NSOperationQueue currentQueue] non uguale a dispatch_get_current_queue () ----- A volte restituisce null ---- dispatch_async (dispatch_get_global_queue (0, 0), ^ {NSLog (@ "q (0, 0) è% @ ", dispatch_get_current_queue ()); NSLog (@" cq (0,0) è% @ ", [NSOperationQueue currentQueue]);}); ----- q (0,0) è <OS_dispatch_queue_root: com.apple.root.default-qos [0x100195140]> cq (0,0) è (null) ----- deprecato o meno dispatch_get_current_queue () sembra per essere l'unica soluzione che vedo per segnalare la coda corrente in tutte le condizioni
godzilla

Risposte:


64

Lo schema di "esecuzione su qualsiasi coda su cui si trovava il chiamante" è allettante, ma alla fine non è una grande idea. Quella coda potrebbe essere una coda a bassa priorità, la coda principale o qualche altra coda con proprietà dispari.

Il mio approccio preferito a questo è dire "il blocco di completamento viene eseguito su una coda definita dall'implementazione con queste proprietà: x, y, z" e lasciare che il blocco venga inviato a una coda particolare se il chiamante desidera un controllo maggiore di quello. Un tipico insieme di proprietà da specificare sarebbe qualcosa come "seriale, non rientrante e asincrono rispetto a qualsiasi altra coda visibile all'applicazione".

** MODIFICARE **

Catfish_Man ha messo un esempio nei commenti qui sotto, lo sto solo aggiungendo alla sua risposta.

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

7
Totalmente d'accordo. Puoi vedere che Apple lo segue sempre; ogni volta che vuoi fare qualcosa sulla coda principale devi sempre spedire alla coda principale perché Apple garantisce sempre che sei su un thread diverso. La maggior parte delle volte stai aspettando che un processo di lunga durata finisca di recuperare / manipolare i dati e quindi puoi elaborarli in background direttamente nel blocco di completamento e quindi incollare solo le chiamate dell'interfaccia utente in un blocco di spedizione sulla coda principale. Inoltre è sempre bene seguire ciò che Apple si prefigge in termini di aspettative poiché gli sviluppatori saranno abituati al modello.
Jack Lawrence

1
ottima risposta .. ma speravo in almeno un codice di esempio per illustrare ciò che stai dicendo
abbood

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

(Per un esempio del tutto banale)
Catfish_Man

3
Non è possibile nel caso generale perché è possibile (e in effetti abbastanza probabile) trovarsi su più di una coda contemporaneamente, a causa di dispatch_sync () e dispatch_set_target_queue (). Sono possibili alcuni sottoinsiemi del caso generale.
Catfish_Man

27

Questo è fondamentalmente l'approccio sbagliato per l'API che stai descrivendo. Se un'API accetta l'esecuzione di un blocco e di un blocco di completamento, devono essere veri i seguenti fatti:

  1. Il "blocco da eseguire" dovrebbe essere eseguito su una coda interna, ad esempio una coda privata per l'API e quindi interamente sotto il controllo di tale API. L'unica eccezione a questo è se l'API dichiara specificamente che il blocco verrà eseguito sulla coda principale o su una delle code simultanee globali.

  2. Il blocco di completamento dovrebbe sempre essere espresso come una tupla (coda, blocco) a meno che le stesse ipotesi di # 1 non siano vere, ad esempio il blocco di completamento verrà eseguito su una coda globale nota. Il blocco di completamento dovrebbe inoltre essere inviato in modo asincrono sulla coda passata.

Questi non sono solo punti stilistici, sono assolutamente necessari se la tua API vuole essere al sicuro da deadlock o altri comportamenti edge-case che un giorno ti bloccheranno dall'albero più vicino. :-)


11
Sembra ragionevole, ma per qualche ragione questo non è l'approccio adottato da Apple per le proprie API: la maggior parte dei metodi che richiedono un blocco di completamento non fanno anche la coda ...
cfischer

2
Vero, e per modificare in qualche modo la mia precedente affermazione, se è palesemente ovvio che il blocco di completamento verrà eseguito sulla coda principale o su una coda simultanea globale. Cambierò la mia risposta per indicare quanto.
jkh

Per commentare il fatto che Apple non abbia adottato questo approccio: Apple non è sempre "giusta" per definizione. Gli argomenti giusti sono sempre più forti di qualsiasi autorità particolare, che qualsiasi scienziato confermerà. Penso che la risposta sopra lo affermi molto bene da una prospettiva di architettura software adeguata.
Werner Altewischer

14

Le altre risposte sono ottime, ma per me la risposta è strutturale. Ho un metodo come questo che è su un Singleton:

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

che ha due dipendenze, che sono:

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

e

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

In questo modo centralizzo le mie chiamate per l'invio sull'altro thread.


12

Dovresti stare attento al tuo utilizzo dispatch_get_current_queuein primo luogo. Dal file di intestazione:

Consigliato solo per scopi di debug e registrazione:

Il codice non deve fare ipotesi sulla coda restituita, a meno che non sia una delle code globali o una coda che il codice stesso ha creato. Il codice non deve presumere che l'esecuzione sincrona su una coda sia sicura da deadlock se quella coda non è quella restituita da dispatch_get_current_queue ().

Puoi fare una delle due cose:

  1. Mantieni un riferimento alla coda su cui hai postato originariamente (se l'hai creata tramite dispatch_queue_create) e usalo da quel momento in poi.

  2. Utilizza le code definite dal sistema tramite dispatch_get_global_queuee tieni traccia di quella che stai utilizzando.

Effettivamente, mentre prima facevi affidamento sul sistema per tenere traccia della coda in cui ti trovi, dovrai farlo da solo.


16
Come possiamo "mantenere un riferimento alla coda su cui hai postato originariamente" se non possiamo usarla dispatch_get_current_queue()per scoprire quale coda è? A volte il codice che deve sapere su quale coda è in esecuzione non ha alcun controllo o conoscenza di esso. Ho un sacco di codice che può (e dovrebbe) essere eseguito su una coda in background, ma occasionalmente ha bisogno di aggiornare la gui (barra di avanzamento, ecc.), E quindi ha bisogno di dispatch_sync () sulla coda principale per quelle operazioni. Se già nella coda principale, dispatch_sync () si bloccherà per sempre. Mi ci vorranno mesi per eseguire il refactoring del mio codice per questo.
Abhi Beckert

3
Penso che NSURLConnection fornisca i callback di completamento sullo stesso thread da cui viene chiamato. Userebbe la stessa API "dispatch_get_current_queue" per memorizzare la coda da cui viene chiamata da utilizzare al momento della richiamata?
defactodeity

5

Apple era deprecata dispatch_get_current_queue(), ma ha lasciato un buco in un altro posto, quindi siamo ancora in grado di ottenere la coda di spedizione corrente:

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

Questo funziona almeno per la coda principale. Nota, quella underlyingQueueproprietà è disponibile da iOS 8.

Se devi eseguire il blocco di completamento nella coda originale, puoi anche usarlo OperationQueuedirettamente, intendo senza GCD.



0

Questa è una risposta anche io. Quindi parlerò del nostro caso d'uso.

Abbiamo un livello di servizi e il livello dell'interfaccia utente (tra gli altri livelli). Il livello dei servizi esegue le attività in background. (Attività di manipolazione dei dati, attività CoreData, chiamate di rete ecc.). Il livello di servizio ha un paio di code di operazioni per soddisfare le esigenze del livello dell'interfaccia utente.

Il livello dell'interfaccia utente si basa sul livello dei servizi per svolgere il proprio lavoro e quindi eseguire un blocco di completamento riuscito. Questo blocco può contenere codice UIKit. Un semplice caso d'uso è ottenere tutti i messaggi dal server e ricaricare la visualizzazione della raccolta.

Qui garantiamo che i blocchi che vengono passati nel livello dei servizi vengono inviati sulla coda su cui è stato richiamato il servizio. Poiché dispatch_get_current_queue è un metodo deprecato, utilizziamo NSOperationQueue.currentQueue per ottenere la coda corrente del chiamante. Nota importante su questa proprietà.

La chiamata a questo metodo dall'esterno del contesto di un'operazione in esecuzione in genere restituisce zero.

Poiché invochiamo sempre i nostri servizi su una coda nota (le nostre code personalizzate e la coda principale), questo funziona bene per noi. Abbiamo casi in cui serviceA può chiamare serviceB che può chiamare serviceC. Poiché controlliamo da dove viene effettuata la prima chiamata di servizio, sappiamo che il resto dei servizi seguirà le stesse regole.

Quindi NSOperationQueue.currentQueue restituirà sempre una delle nostre code o MainQueue.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.