¿Alternativas a dispatch_get_current_queue () para bloques de finalización en iOS 6?

101

Tengo un método que acepta un bloque y un bloque de finalización. El primer bloque debería ejecutarse en segundo plano, mientras que el bloque de finalización debería ejecutarse en cualquier cola a la que se haya llamado al método.

Para este último siempre lo usé dispatch_get_current_queue(), pero parece que está obsoleto en iOS 6 o superior. ¿Qué debo usar en su lugar?

cfischer
fuente
¿Por qué dices que dispatch_get_current_queue()está obsoleto en iOS 6? los doctores no dicen nada al respecto
jere
3
El compilador se queja de ello. Intentalo.
cfischer
4
@jere Verifique el archivo de encabezado, indica que está depurado
WDUK
Aparte de las discusiones sobre las mejores prácticas, veo [NSOperationQueue currentQueue] que puede responder a la pregunta. No estoy seguro de las advertencias sobre su uso.
Matt
advertencia encontrada ------ [NSOperationQueue currentQueue] no es lo mismo que dispatch_get_current_queue () ----- A veces devuelve null ---- dispatch_async (dispatch_get_global_queue (0, 0), ^ {NSLog (@ "q (0, 0) es% @ ", dispatch_get_current_queue ()); NSLog (@" cq (0,0) es% @ ", [NSOperationQueue currentQueue]);}); ----- q (0,0) es <OS_dispatch_queue_root: com.apple.root.default-qos [0x100195140]> cq (0,0) es (nulo) ----- está descartado o no dispatch_get_current_queue () parece para ser la única solución que veo para informar la cola actual en todas las condiciones
godzilla

Respuestas:

64

El patrón de "ejecutar en cualquier cola en la que estuviera la persona que llama" es atractivo, pero en última instancia no es una gran idea. Esa cola podría ser una cola de baja prioridad, la cola principal o alguna otra cola con propiedades extrañas.

Mi enfoque favorito para esto es decir "el bloque de finalización se ejecuta en una cola definida por la implementación con estas propiedades: x, y, z", y dejar que el bloque se envíe a una cola en particular si la persona que llama quiere más control que eso. Un conjunto típico de propiedades para especificar sería algo así como "serial, no reentrante y asincrónico con respecto a cualquier otra cola visible de la aplicación".

** EDITAR **

Catfish_Man puso un ejemplo en los comentarios a continuación, solo lo estoy agregando a su respuesta.

- (void) aMethodWithCompletionBlock:(dispatch_block_t)completionHandler     
{ 
    dispatch_async(self.workQueue, ^{ 
        [self doSomeWork]; 
        dispatch_async(self.callbackQueue, completionHandler); 
    } 
}
Hombre_Bagre
fuente
7
Totalmente de acuerdo. Puede ver que Apple siempre sigue esto; siempre que quiera hacer algo en la cola principal, siempre debe enviar a la cola principal porque Apple siempre garantiza que está en un hilo diferente. La mayoría de las veces, está esperando que un proceso de larga ejecución termine de obtener / manipular datos y luego puede procesarlos en segundo plano directamente en su bloque de finalización y luego solo pegar las llamadas de IU en un bloque de despacho en la cola principal. Además, siempre es bueno seguir lo que Apple establece en términos de expectativas, ya que los desarrolladores estarán acostumbrados al patrón.
Jack Lawrence
1
gran respuesta ... pero esperaba al menos algún código de muestra para ilustrar lo que estás diciendo
abbood
3
- (void) aMethodWithCompletionBlock: (dispatch_block_t) CompletionHandler {dispatch_async (self.workQueue, ^ {[self doSomeWork]; dispatch_async (self.callbackQueue, completeHandler);}}
Catfish_Man
(Para un ejemplo completamente trivial)
Catfish_Man
3
No es posible en el caso general porque es posible (y de hecho bastante probable) estar en más de una cola simultáneamente, debido a dispatch_sync () y dispatch_set_target_queue (). Hay algunos subconjuntos del caso general que son posibles.
Catfish_Man
27

Este es fundamentalmente el enfoque incorrecto para la API que está describiendo. Si una API acepta un bloque y un bloque de finalización para ejecutarse, los siguientes hechos deben ser ciertos:

  1. El "bloque para ejecutar" debe ejecutarse en una cola interna, por ejemplo, una cola que es privada para la API y, por lo tanto, está completamente bajo el control de esa API. La única excepción a esto es si la API declara específicamente que el bloque se ejecutará en la cola principal o en una de las colas simultáneas globales.

  2. El bloque de finalización siempre debe expresarse como una tupla (cola, bloque) a menos que se cumplan las mismas suposiciones que para el n. ° 1, por ejemplo, el bloque de finalización se ejecutará en una cola global conocida. Además, el bloque de finalización debe enviarse de forma asíncrona en la cola pasada.

Estos no son solo puntos estilísticos, son completamente necesarios si su API va a estar a salvo de interbloqueos u otros comportamientos de casos extremos que, de lo contrario, lo colgarán del árbol más cercano algún día. :-)

jkh
fuente
11
Suena razonable, pero por alguna razón ese no es el enfoque adoptado por Apple para sus propias API: la mayoría de los métodos que toman un bloque de finalización no también toman una cola ...
cfischer
2
Es cierto, y para modificar un poco mi afirmación anterior, si es manifiestamente obvio que el bloque de finalización se ejecutará en la cola principal o en una cola simultánea global. Cambiaré mi respuesta para indicar tanto.
jkh
Para comentar que Apple no ha adoptado ese enfoque: Apple no siempre tiene la "razón" por definición. Los argumentos adecuados son siempre más fuertes que cualquier autoridad en particular, lo que cualquier científico confirmará. Creo que la respuesta anterior lo dice muy bien desde una perspectiva de arquitectura de software adecuada.
Werner Altewischer
14

Las otras respuestas son geniales, pero para mí la respuesta es estructural. Tengo un método como este que está en un Singleton:

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

que tiene dos dependencias, que son:

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

y

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

De esa manera, centralizo mis llamadas para enviarlas en el otro hilo.

Dan Rosenstark
fuente
12

Debe tener cuidado con el uso de dispatch_get_current_queueen primer lugar. Desde el archivo de encabezado:

Recomendado solo para fines de depuración y registro:

El código no debe hacer suposiciones sobre la cola devuelta, a menos que sea una de las colas globales o una cola que el propio código haya creado. El código no debe asumir que la ejecución síncrona en una cola está a salvo de un punto muerto si esa cola no es la devuelta por dispatch_get_current_queue ().

Puede hacer una de estas dos cosas:

  1. Mantenga una referencia a la cola en la que publicó originalmente (si la creó a través de dispatch_queue_create) y úsela a partir de ese momento.

  2. Utilice las colas definidas por el sistema a través de dispatch_get_global_queuey lleve un registro de cuál está utilizando.

Efectivamente, mientras confiaba anteriormente en el sistema para realizar un seguimiento de la cola en la que se encuentra, tendrá que hacerlo usted mismo.

WDUK
fuente
16
¿Cómo podemos "mantener una referencia a la cola en la que publicaste originalmente" si no podemos usar dispatch_get_current_queue()para averiguar qué cola es? A veces, el código que necesita saber en qué cola se está ejecutando no tiene ningún control o conocimiento de ello. Tengo una gran cantidad de código que puede (y debe) ejecutarse en una cola en segundo plano, pero ocasionalmente necesito actualizar la interfaz gráfica de usuario (barra de progreso, etc.) y, por lo tanto, necesito dispatch_sync () a la cola principal para esas operaciones. Si ya está en la cola principal, dispatch_sync () se bloqueará para siempre. Me llevará meses refactorizar mi código para esto.
Abhi Beckert
3
Creo que NSURLConnection da sus devoluciones de llamada de finalización en el mismo hilo desde el que se llama. ¿Usaría la misma API "dispatch_get_current_queue" para almacenar la cola desde la que se llama para usarla en el momento de la devolución de llamada?
defactodeity
5

Apple se había desaprobado dispatch_get_current_queue(), pero dejó un agujero en otro lugar, por lo que aún podemos obtener la cola de envío actual:

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

Esto funciona al menos para la cola principal. Tenga en cuenta queunderlyingQueue propiedad está disponible desde iOS 8.

Si necesita realizar el bloque de finalización en la cola original, también puede usarlo OperationQueuedirectamente, es decir, sin GCD.

Kelin
fuente
0

Esta es una respuesta yo también. Entonces hablaré sobre nuestro caso de uso.

Tenemos una capa de servicios y la capa de UI (entre otras capas). La capa de servicios ejecuta tareas en segundo plano. (Tareas de manipulación de datos, tareas CoreData, llamadas de red, etc.). La capa de servicio tiene un par de colas de operaciones para satisfacer las necesidades de la capa de interfaz de usuario.

La capa de interfaz de usuario se basa en la capa de servicios para hacer su trabajo y luego ejecutar un bloque de finalización satisfactoria. Este bloque puede tener código UIKit. Un caso de uso simple es obtener todos los mensajes del servidor y volver a cargar la vista de colección.

Aquí garantizamos que los bloques que se pasan a la capa de servicios se envían en la cola en la que se invocó el servicio. Dado que dispatch_get_current_queue es un método obsoleto, usamos NSOperationQueue.currentQueue para obtener la cola actual de la persona que llama. Nota importante sobre esta propiedad.

Llamar a este método desde fuera del contexto de una operación en ejecución normalmente da como resultado que no se devuelva nada.

Dado que siempre invocamos nuestros servicios en una cola conocida (nuestras colas personalizadas y cola principal), esto funciona bien para nosotros. Tenemos casos en los que el servicio A puede llamar al servicio B que puede llamar al servicio C. Dado que controlamos desde dónde se realiza la primera llamada de servicio, sabemos que el resto de los servicios seguirán las mismas reglas.

Entonces NSOperationQueue.currentQueue siempre devolverá una de nuestras Queues o MainQueue.

Kris Subramanian
fuente