Eu vejo dois padrões comuns para blocos no Objective-C. Um é um par de sucesso: / falha: blocos, o outro é uma única conclusão: bloco.
Por exemplo, digamos que eu tenho uma tarefa que retornará um objeto de forma assíncrona e essa tarefa poderá falhar. O primeiro padrão é -taskWithSuccess:(void (^)(id object))success failure:(void (^)(NSError *error))failure
. O segundo padrão é -taskWithCompletion:(void (^)(id object, NSError *error))completion
.
Não houve sucesso:
[target taskWithSuccess:^(id object) {
// W00t! I've got my object
} failure:^(NSError *error) {
// Oh noes! report the failure.
}];
conclusão:
[target taskWithCompletion:^(id object, NSError *error) {
if (object) {
// W00t! I've got my object
} else {
// Oh noes! report the failure.
}
}];
Qual é o padrão preferido? Quais são os pontos fortes e fracos? Quando você usaria um sobre o outro?
design-patterns
objective-c
Jeffery Thomas
fonte
fonte
Respostas:
O retorno de chamada de conclusão (em oposição ao par de sucesso / falha) é mais genérico. Se você precisar preparar algum contexto antes de lidar com o status de retorno, poderá fazê-lo antes da cláusula "if (object)". No caso de sucesso / falha, você deve duplicar esse código. Isso depende da semântica de retorno de chamada, é claro.
fonte
-task…
pudesse retornar o objeto, mas o objeto não estiver no estado correto, você ainda precisaria de tratamento de erros na condição de sucesso.Eu diria que, se a API fornece um manipulador de conclusão ou um par de blocos de sucesso / falha, é principalmente uma questão de preferência pessoal.
Ambas as abordagens têm prós e contras, embora haja apenas diferenças marginais.
Considere-se que existem também outras variantes, por exemplo, onde o um manipulador de conclusão pode ter apenas um parâmetro combinando o eventual resultado ou um erro potencial:
O objetivo desta assinatura é que um manipulador de conclusão possa ser usado genericamente em outras APIs.
Por exemplo, em Categoria para NSArray, existe um método
forEachApplyTask:completion:
que chama seqüencialmente uma tarefa para cada objeto e quebra o loop IFF, houve um erro. Como esse método também é assíncrono, ele também possui um manipulador de conclusão:De fato,
completion_t
conforme definido acima, é genérico e suficiente para lidar com todos os cenários.No entanto, existem outros meios para uma tarefa assíncrona sinalizar sua notificação de conclusão ao site de chamada:
Promessas
Promessas, também chamadas de "Futuros", "Adiadas" ou "Atrasadas" representam o resultado final de uma tarefa assíncrona (consulte também: wiki Futuros e promessas ).
Inicialmente, uma promessa está no estado "pendente". Ou seja, seu "valor" ainda não foi avaliado e ainda não está disponível.
No Objective-C, uma Promessa seria um objeto comum que será retornado de um método assíncrono, como mostrado abaixo:
Enquanto isso, as tarefas assíncronas começam a avaliar seu resultado.
Observe também que não há manipulador de conclusão. Em vez disso, a Promessa fornecerá um meio mais poderoso para que o site de chamadas possa obter o resultado final da tarefa assíncrona, que veremos em breve.
A tarefa assíncrona, que criou o objeto de promessa, DEVE eventualmente "resolver" sua promessa. Isso significa que, uma vez que uma tarefa pode ter êxito ou falhar, DEVE "cumprir" uma promessa passando o resultado avaliado ou DEVE "rejeitar" a promessa passando um erro indicando o motivo da falha.
Quando uma promessa é resolvida, ela não pode mais mudar seu estado, incluindo seu valor.
Depois que uma promessa é resolvida, um site de chamada pode obter o resultado (se falhou ou teve êxito). Como isso é feito depende se a promessa é implementada usando o estilo síncrono ou assíncrono.
A Promise pode ser implementado em um síncrono ou assíncrono um modelo que leva a qualquer bloqueio , respectivamente, sem bloqueio semântica.
Em um estilo síncrono para recuperar o valor da promessa, um site de chamada usaria um método que bloqueará o encadeamento atual até que a promessa tenha sido resolvida pela tarefa assíncrona e o resultado final esteja disponível.
Em um estilo assíncrono, o site de chamada registraria retornos de chamada ou blocos de manipulador que são chamados imediatamente após a promessa ter sido resolvida.
Verificou-se que o estilo síncrono tem várias desvantagens significativas que efetivamente derrotam os méritos das tarefas assíncronas. Um artigo interessante sobre a implementação atualmente incorreta de "futuros" na lib padrão do C ++ 11 pode ser lida aqui: Promessas quebradas - futuros do C ++ 0x .
Como, no Objective-C, um site de chamadas obteria o resultado?
Bem, provavelmente é melhor mostrar alguns exemplos. Existem algumas bibliotecas que implementam uma promessa (veja os links abaixo).
No entanto, para os próximos trechos de código, usarei uma implementação específica de uma biblioteca Promise, disponível no GitHub RXPromise . Eu sou o autor de RXPromise.
As outras implementações podem ter uma API semelhante, mas pode haver diferenças pequenas e possivelmente sutis na sintaxe. RXPromise é uma versão Objective-C da especificação Promise / A + que define um padrão aberto para implementações robustas e interoperáveis de promessas em JavaScript.
Todas as bibliotecas promissoras listadas abaixo implementam o estilo assíncrono.
Existem diferenças bastante significativas entre as diferentes implementações. O RXPromise utiliza internamente a biblioteca de despacho, é totalmente seguro para threads, extremamente leve e também fornece vários recursos úteis adicionais, como cancelamento.
Um site de chamada obtém o resultado final da tarefa assíncrona por meio de "registradores" de manipuladores. A "especificação Promise / A +" define o método
then
.O método
then
Com o RXPromise, tem a seguinte aparência:
onde successHandler é um bloco que é chamado quando a promessa é "cumprida" e errorHandler é um bloco que é chamado quando a promessa é "rejeitada".
No RXPromise, os blocos manipuladores têm a seguinte assinatura:
O success_handler possui um resultado de parâmetro que é obviamente o resultado final da tarefa assíncrona. Da mesma forma, o error_handler possui um erro de parâmetro, que é o erro relatado pela tarefa assíncrona quando falhou.
Ambos os blocos têm um valor de retorno. O significado desse valor de retorno ficará claro em breve.
No RXPromise,
then
é uma propriedade que retorna um bloco. Este bloco possui dois parâmetros, o bloco manipulador de sucesso e o bloco manipulador de erro. Os manipuladores devem ser definidos pelo site de chamada.Portanto, a expressão
promise.then(success_handler, error_handler);
é uma forma curta dePodemos escrever um código ainda mais conciso:
O código diz: "Execute doSomethingAsync, quando for bem-sucedido, depois execute o manipulador de sucesso".
Aqui, o manipulador de erros é o
nil
que significa que, em caso de erro, ele não será tratado nesta promessa.Outro fato importante é que chamar o bloco retornado da propriedade
then
retornará uma promessa:Ao chamar o bloco retornado da propriedade
then
, o "destinatário" retorna uma nova promessa, uma promessa filho . O receptor se torna a promessa dos pais .O que isso significa?
Bem, devido a isso, podemos "encadear" tarefas assíncronas que efetivamente são executadas sequencialmente.
Além disso, o valor de retorno de qualquer manipulador se tornará o "valor" da promessa retornada. Portanto, se a tarefa tiver êxito com o resultado final @ “OK”, a promessa retornada será “resolvida” (que é “cumprida”) com o valor @ “OK”:
Da mesma forma, quando a tarefa assíncrona falhar, a promessa retornada será resolvida (que é "rejeitada") com um erro.
O manipulador também pode retornar outra promessa. Por exemplo, quando esse manipulador executa outra tarefa assíncrona. Com esse mecanismo, podemos "encadear" tarefas assíncronas:
Se não houver promessa filho, o valor de retorno não terá efeito.
Um exemplo mais complexo:
Aqui, nós executamos
asyncTaskA
,asyncTaskB
,asyncTaskC
easyncTaskD
sequencialmente - e cada tarefa subseqüente leva o resultado da tarefa anterior como entrada:Essa "cadeia" também é chamada de "continuação".
Tratamento de erros
As promessas facilitam especialmente o manuseio de erros. Os erros serão "encaminhados" do pai para o filho se não houver um manipulador de erros definido na promessa do pai. O erro será encaminhado pela cadeia até que uma criança lide com isso. Assim, tendo a cadeia acima, podemos implementar o tratamento de erros apenas adicionando outra “continuação” que lida com um erro em potencial que pode ocorrer em qualquer lugar acima :
Isso é semelhante ao estilo síncrono provavelmente mais familiar com o tratamento de exceções:
As promessas em geral têm outros recursos úteis:
Por exemplo, tendo uma referência a uma promessa,
then
é possível "registrar" quantos manipuladores desejar. No RXPromise, os manipuladores de registro podem ocorrer a qualquer momento e a partir de qualquer encadeamento, pois é totalmente seguro para encadeamento.O RXPromise possui alguns recursos funcionais mais úteis, não exigidos pela especificação Promise / A +. Um é "cancelamento".
Descobriu-se que o "cancelamento" é uma característica valiosa e importante. Por exemplo, um site de chamada com uma referência a uma promessa pode enviar a
cancel
mensagem para indicar que não está mais interessado no resultado final.Imagine uma tarefa assíncrona que carrega uma imagem da web e que deve ser exibida em um controlador de exibição. Se o usuário se afastar do controlador de exibição atual, o desenvolvedor poderá implementar o código que envia uma mensagem de cancelamento para o imagePromise , que, por sua vez, aciona o manipulador de erros definido pela Operação de Solicitação HTTP, onde a solicitação será cancelada.
No RXPromise, uma mensagem de cancelamento será encaminhada apenas de um pai para seus filhos, mas não vice-versa. Ou seja, uma promessa "raiz" cancelará todas as promessas de crianças. Mas uma promessa infantil só cancelará o “ramo” onde é o pai. A mensagem de cancelamento também será encaminhada às crianças se uma promessa já tiver sido resolvida.
Uma tarefa assíncrona pode -se registar manipulador para a sua própria promessa, e, assim, pode detectar quando alguém o cancelou. Pode então parar prematuramente de executar uma tarefa possivelmente longa e cara.
Aqui estão algumas outras implementações de Promises no Objective-C encontradas no GitHub:
https://github.com/Schoonology/aplus-objc
https://github.com/affablebloke/deferred-objective-c
https://github.com/bww/FutureKit
https://github.com/jkubicek/JKPromises
https://github.com/Strilanc/ObjC-CollapsingFutures
https://github.com/b52/OMPromises
https://github.com/mproberts/objc-promise
https://github.com/klaaspieter/Promise
https: //github.com/jameswomack/Promise
https://github.com/nilfs/promise-objc
https://github.com/mxcl/PromiseKit
https://github.com/apleshkov/promises-aplus
https: // github.com/KptainO/Rebelle
e minha própria implementação: RXPromise .
Esta lista provavelmente não está completa!
Ao escolher uma terceira biblioteca para o seu projeto, verifique cuidadosamente se a implementação da biblioteca segue os pré-requisitos listados abaixo:
Uma biblioteca de promessas confiável DEVE ser segura para threads!
É tudo sobre processamento assíncrono, e queremos utilizar várias CPUs e executar em diferentes threads simultaneamente, sempre que possível. Tenha cuidado, a maioria das implementações não é segura para threads!
Os manipuladores devem ser chamados de forma assíncrona, no que diz respeito ao local da chamada! Sempre e não importa o que aconteça!
Qualquer implementação decente também deve seguir um padrão muito rigoroso ao chamar as funções assíncronas. Muitos implementadores tendem a "otimizar" o caso, em que um manipulador será chamado de forma síncrona quando a promessa já estiver resolvida quando o manipulador será registrado. Isso pode causar todos os tipos de problemas. Consulte Não liberte o Zalgo! .
Também deve haver um mecanismo para cancelar uma promessa.
A possibilidade de cancelar uma tarefa assíncrona geralmente se torna um requisito com alta prioridade na análise de requisitos. Caso contrário, com certeza haverá uma solicitação de aprimoramento do usuário algum tempo depois após o lançamento do aplicativo. O motivo deve ser óbvio: qualquer tarefa que possa parar ou demorar muito para terminar deve ser cancelável pelo usuário ou por um tempo limite. Uma biblioteca de promessas decente deve apoiar o cancelamento.
fonte
Sei que essa é uma pergunta antiga, mas preciso respondê-la porque minha resposta é diferente das demais.
Para aqueles que dizem que é uma questão de preferência pessoal, tenho que discordar. Existe uma boa razão lógica para preferir um ao outro ...
No caso de conclusão, seu bloco recebe dois objetos, um representa sucesso enquanto o outro representa falha ... Então, o que você faz se ambos são nulos? O que você faz se ambos têm um valor? Essas são perguntas que podem ser evitadas no momento da compilação e, como tal, deveriam ser. Você evita essas perguntas tendo dois blocos separados.
Ter blocos de sucesso e falha separados torna seu código estaticamente verificável.
Note que as coisas mudam com Swift. Nele, podemos implementar a noção de uma
Either
enumeração para garantir que o único bloco de conclusão tenha um objeto ou um erro e tenha exatamente um deles. Portanto, para Swift, um único bloco é melhor.fonte
Eu suspeito que vai acabar sendo uma preferência pessoal ...
Mas eu prefiro os blocos separados de sucesso / falha. Eu gosto de separar a lógica do sucesso / falha. Se você tivesse aninhado sucesso / fracassos, acabaria com algo que seria mais legível (na minha opinião pelo menos).
Como um exemplo relativamente extremo desse aninhamento, aqui está um Ruby mostrando esse padrão.
fonte
Parece uma cópia completa, mas não acho que haja uma resposta certa aqui. Fui com o bloco de conclusão simplesmente porque o tratamento de erros ainda precisa ser feito na condição de sucesso ao usar blocos de sucesso / falha.
Eu acho que o código final será algo como
ou simplesmente
Não é o melhor pedaço de código e o aninhamento fica pior
Acho que vou ficar deprimido por um tempo.
fonte