Alternativas para dispatch_get_current_queue () para blocos de conclusão no iOS 6?

101

Eu tenho um método que aceita um bloco e um bloco de conclusão. O primeiro bloco deve ser executado em segundo plano, enquanto o bloco de conclusão deve ser executado em qualquer fila em que o método foi chamado.

Sempre usei este último dispatch_get_current_queue(), mas parece que está obsoleto no iOS 6 ou superior. O que devo usar no lugar?

cfischer
fonte
por que você diz que dispatch_get_current_queue()está obsoleto no iOS 6? os documentos não dizem nada sobre isso
jere
3
O compilador reclama disso. Tente.
cfischer
4
@jere Verifique o arquivo de cabeçalho, ele afirma que está obsoleto
WDUK
Além das discussões sobre quais são as melhores práticas, vejo [NSOperationQueue currentQueue] que pode responder à pergunta. Não tenho certeza sobre advertências quanto ao seu uso.
Matt
advertência encontrada ------ [NSOperationQueue currentQueue] diferente de dispatch_get_current_queue () ----- Às vezes retorna nulo ---- 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) é (nulo) ----- depricated ou não dispatch_get_current_queue () parece para ser a única solução que vejo para relatar a fila atual em todas as condições
godzilla

Respostas:

64

O padrão de "executar em qualquer fila em que o chamador estiver" é atraente, mas, em última análise, não é uma boa ideia. Essa fila pode ser uma fila de baixa prioridade, a fila principal ou alguma outra fila com propriedades estranhas.

Minha abordagem favorita para isso é dizer "o bloco de conclusão é executado em uma fila definida pela implementação com estas propriedades: x, y, z" e deixar o bloco ser despachado para uma fila específica se o chamador quiser mais controle do que isso. Um conjunto típico de propriedades a serem especificadas seria algo como "serial, não reentrante e assíncrono em relação a qualquer outra fila visível do aplicativo".

** EDITAR **

Catfish_Man colocou um exemplo nos comentários abaixo, estou apenas adicionando na resposta dele.

- (void) aMethodWithCompletionBlock:(dispatch_block_t)completionHandler     
{ 
    dispatch_async(self.workQueue, ^{ 
        [self doSomeWork]; 
        dispatch_async(self.callbackQueue, completionHandler); 
    } 
}
Catfish_Man
fonte
7
Totalmente de acordo. Você pode ver que a Apple sempre segue isso; sempre que você quiser fazer algo na fila principal, você sempre precisará despachar para a fila principal, porque a apple sempre garante que você está em um thread diferente. Na maior parte do tempo, você está esperando que um processo de longa execução termine de buscar / manipular dados e, em seguida, pode processá-lo em segundo plano diretamente no seu bloco de conclusão e, em seguida, colocar apenas chamadas de IU em um bloco de despacho na fila principal. Além disso, é sempre bom seguir o que a Apple define em termos de expectativas, uma vez que os desenvolvedores estarão acostumados com o padrão.
Jack Lawrence
1
ótima resposta .. mas eu esperava pelo menos algum código de amostra para ilustrar o que você está dizendo
abbood
3
- (void) aMethodWithCompletionBlock: (dispatch_block_t) completedHandler {dispatch_async (self.workQueue, ^ {[self doSomeWork]; dispatch_async (self.callbackQueue, completedHandler);}}
Catfish_Man
(Para um exemplo completamente trivial)
Catfish_Man
3
Não é possível no caso geral porque é possível (e de fato bastante provável) estar em mais de uma fila simultaneamente, devido a dispatch_sync () e dispatch_set_target_queue (). Existem alguns subconjuntos do caso geral que são possíveis.
Catfish_Man
27

Esta é fundamentalmente a abordagem errada para a API que você está descrevendo. Se uma API aceita um bloco e um bloco de conclusão para execução, os seguintes fatos precisam ser verdadeiros:

  1. O "bloco a ser executado" deve ser executado em uma fila interna, por exemplo, uma fila privada para a API e, portanto, totalmente sob o controle dessa API. A única exceção é se a API declara especificamente que o bloco será executado na fila principal ou em uma das filas simultâneas globais.

  2. O bloco de conclusão deve sempre ser expresso como uma tupla (fila, bloco), a menos que as mesmas suposições do nº 1 sejam verdadeiras, por exemplo, o bloco de conclusão será executado em uma fila global conhecida. Além disso, o bloco de conclusão deve ser despachado de forma assíncrona na fila passada.

Esses não são apenas pontos estilísticos, eles são totalmente necessários se a sua API deve estar protegida de impasses ou outro comportamento de caso extremo que, de outra forma, O deixará pendurado na árvore mais próxima algum dia. :-)

jkh
fonte
11
Parece razoável, mas por algum motivo essa não é a abordagem adotada pela Apple para suas próprias APIs: a maioria dos métodos que usam um bloco de conclusão também não ocupam uma fila ...
cfischer
2
Verdade, e para modificar minha afirmação anterior um pouco, se for manifestamente óbvio que o bloco de conclusão será executado na fila principal ou em uma fila simultânea global. Vou mudar minha resposta para indicar isso.
jkh
Para comentar sobre a Apple não seguir essa abordagem: a Apple nem sempre está "certa" por definição. Os argumentos adequados são sempre mais fortes do que qualquer autoridade em particular, o que qualquer cientista confirmará. Acho que a resposta acima afirma isso muito bem de uma perspectiva de arquitetura de software adequada.
Werner Altewischer
14

As outras respostas são ótimas, mas para mim a resposta é estrutural. Eu tenho um método como este que está em um Singleton:

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

que tem duas dependências, que são:

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

Dessa forma, centralizo minhas chamadas para despachar no outro segmento.

Dan Rosenstark
fonte
12

Você deve ter cuidado com o uso de dispatch_get_current_queue em primeiro lugar. Do arquivo de cabeçalho:

Recomendado apenas para fins de depuração e registro:

O código não deve fazer suposições sobre a fila retornada, a menos que seja uma das filas globais ou uma fila que o próprio código tenha criado. O código não deve assumir que a execução síncrona em uma fila está protegida de deadlock se essa fila não for a retornada por dispatch_get_current_queue ().

Você pode fazer uma das duas coisas:

  1. Mantenha uma referência à fila que você postou originalmente (se você a criou via dispatch_queue_create) e use-a a partir de então.

  2. Use as filas definidas pelo sistema por meio de dispatch_get_global_queuee mantenha um controle de qual você está usando.

Efetivamente, embora antes contasse com o sistema para manter o controle da fila em que se encontra, você terá que fazer isso sozinho.

WDUK
fonte
16
Como podemos "manter uma referência à fila em que você postou originalmente" se não podemos usar dispatch_get_current_queue()para descobrir que fila é essa? Às vezes, o código que precisa saber em qual fila está sendo executado não tem nenhum controle ou conhecimento sobre ele. Eu tenho muito código que pode (e deve) ser executado em uma fila de segundo plano, mas ocasionalmente precisa atualizar a gui (barra de progresso, etc) e, portanto, precisa enviar dispatch_sync () para a fila principal para essas operações. Se já estiver na fila principal, dispatch_sync () irá travar para sempre. Vou levar meses para refatorar meu código para isso.
Abhi Beckert
3
Acho que NSURLConnection dá seus retornos de chamada de conclusão no mesmo segmento de onde é chamado. Estaria usando a mesma API "dispatch_get_current_queue" para armazenar a fila da qual é chamado para ser usado no momento do retorno de chamada?
defactodeity
5

A Apple foi descontinuada dispatch_get_current_queue(), mas deixou uma lacuna em outro lugar, então ainda podemos obter a fila de despacho atual:

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

Isso funciona para a fila principal, pelo menos. Observe que essa underlyingQueuepropriedade está disponível desde iOS 8.

Se precisar realizar o bloco de completamento na fila original, também pode usar OperationQueuediretamente, quer dizer sem GCD.

Kelin
fonte
0

Esta é uma resposta do tipo eu também. Vou falar sobre nosso caso de uso.

Temos uma camada de serviços e a camada de IU (entre outras camadas). A camada de serviços executa tarefas em segundo plano. (Tarefas de manipulação de dados, tarefas CoreData, chamadas de rede, etc.). A camada de serviço tem algumas filas de operação para satisfazer as necessidades da camada de IU.

A camada de IU depende da camada de serviços para fazer seu trabalho e, em seguida, executar um bloco de conclusão de sucesso. Este bloco pode conter o código UIKit. Um caso de uso simples é obter todas as mensagens do servidor e recarregar a visualização da coleção.

Aqui, garantimos que os blocos que são passados ​​para a camada de serviços são despachados na fila em que o serviço foi chamado. Como dispatch_get_current_queue é um método obsoleto, usamos o NSOperationQueue.currentQueue para obter a fila atual do chamador. Nota importante sobre esta propriedade.

Chamar esse método de fora do contexto de uma operação em execução normalmente resulta no retorno de nil.

Como sempre chamamos nossos serviços em uma fila conhecida (Nossas filas personalizadas e Fila principal), isso funciona bem para nós. Temos casos em que o serviçoA pode chamar o serviçoB, que pode chamar o serviçoC. Como controlamos de onde a primeira chamada de serviço está sendo feita, sabemos que o restante dos serviços seguirá as mesmas regras.

Portanto, NSOperationQueue.currentQueue sempre retornará uma de nossas Filas ou MainQueue.

Kris Subramanian
fonte