Como simplificar a lógica de retorno de chamada com um bloco?

Digamos que eu precise me comunicar com uma class que forneça um protocolo e chame methods de delegação quando uma operação for concluída, assim:

@protocol SomeObjectDelegate @required - (void)stuffDone:(id)anObject; - (void)stuffFailed; @end @interface SomeObject : NSObject { } @end 

Agora, eu decidi que enquanto eu poderia fazer outra class implementar o método stuffDone: delegate, eu decidi que eu prefiro encapsular o processo em um bloco que é escrito em algum lugar perto de onde SomeObject é instanciado, chamado, etc. Como eu posso fazer isso? Ou, em outras palavras, se você observar este artigo famoso sobre blocos (na seção Substituir retornos de chamada); Como eu poderia escrever um método em SomeObject que aceita um completionHandler: of sort?

Parece que você deseja se comunicar com uma class existente que é projetada para receber um object delegado. Existem várias abordagens, incluindo:

  1. usando uma categoria para adicionar variantes baseadas em blocos dos methods apropriados;
  2. use uma class derivada para adicionar as variantes baseadas em blocos; e
  3. escreva uma class que implemente o protocolo e chame seus blocos.

Aqui está uma maneira de fazer (3). Primeiro vamos supor que seu SomeObject é:

 @protocol SomeObjectDelegate @required - (void)stuffDone:(id)anObject; - (void)stuffFailed; @end @interface SomeObject : NSObject { } + (void) testCallback:(id)delegate; @end @implementation SomeObject + (void) testCallback:(id)delegate { [delegate stuffDone:[NSNumber numberWithInt:42]]; [delegate stuffFailed]; } @end 

então temos alguma maneira de testar – você terá um SomeObject real.

Agora defina uma class que implemente o protocolo e chame seus blocos fornecidos:

 #import "SomeObject.h" typedef void (^StuffDoneBlock)(id anObject); typedef void (^StuffFailedBlock)(); @interface SomeObjectBlockDelegate : NSObject { StuffDoneBlock stuffDoneCallback; StuffFailedBlock stuffFailedCallback; } - (id) initWithOnDone:(StuffDoneBlock)done andOnFail:(StuffFailedBlock)fail; - (void)dealloc; + (SomeObjectBlockDelegate *) someObjectBlockDelegateWithOnDone:(StuffDoneBlock)done andOnFail:(StuffFailedBlock)fail; // protocol - (void)stuffDone:(id)anObject; - (void)stuffFailed; @end 

Essa class salva os blocos que você passa e os chama em resposta aos retornos de chamada do protocolo. A implementação é direta:

 @implementation SomeObjectBlockDelegate - (id) initWithOnDone:(StuffDoneBlock)done andOnFail:(StuffFailedBlock)fail { if (self = [super init]) { // copy blocks onto heap stuffDoneCallback = Block_copy(done); stuffFailedCallback = Block_copy(fail); } return self; } - (void)dealloc { Block_release(stuffDoneCallback); Block_release(stuffFailedCallback); [super dealloc]; } + (SomeObjectBlockDelegate *) someObjectBlockDelegateWithOnDone:(StuffDoneBlock)done andOnFail:(StuffFailedBlock)fail { return (SomeObjectBlockDelegate *)[[[SomeObjectBlockDelegate alloc] initWithOnDone:done andOnFail:fail] autorelease]; } // protocol - (void)stuffDone:(id)anObject { stuffDoneCallback(anObject); } - (void)stuffFailed { stuffFailedCallback(); } @end 

A única coisa que você precisa lembrar é Block_copy () os blocos ao inicializar e Block_release () eles depois – isso ocorre porque os blocos são alocados na pilha e seu object pode sobreviver à criação do quadro de pilha; Block_copy () cria uma cópia no heap.

Agora você pode usar um método baseado em delegação para passar blocos:

 [SomeObject testCallback:[SomeObjectBlockDelegate someObjectBlockDelegateWithOnDone:^(id anObject) { NSLog(@"Done: %@", anObject); } andOnFail:^{ NSLog(@"Failed"); } ] ]; 

Você pode usar essa técnica para agrupar blocos de qualquer protocolo.

Adendo da ARC

Em resposta ao comentário: para tornar este ARC compatível basta remover as chamadas para Block_copy() deixando as atribuições diretas:

 stuffDoneCallback = done; stuffFailedCallback = fail; 

e remova o método dealloc . Você também pode alterar o Blockcopy para copy , isto é, stuffDoneCallback = [done copy]; e isso é o que você pode assumir que é necessário ao ler a documentação do ARC. No entanto, não é como a atribuição é para uma variável forte que faz com que o ARC retenha o valor atribuído – e a retenção de um bloco de pilha o copia para o heap. Portanto, o código ARC gerado produz os mesmos resultados com ou sem a copy .

Você poderia fazer algo assim:

 typedef void (^AZCallback)(NSError *); AZCallback callback = ^(NSError *error) { if (error == nil) { NSLog(@"succeeded!"); } else { NSLog(@"failed: %@", error); } }; SomeObject *o = [[SomeObject alloc] init]; [o setCallback:callback]; // you *MUST* -copy the block [o doStuff]; ...etc; 

Então dentro do SomeObject , você poderia fazer:

 if ([self hadError]) { callback([self error]); } else { callback(nil); } 

O link abaixo explica como as chamadas de retorno usando delegates podem ser facilmente substituídas por blocos.

Os exemplos incluem UITableview, UIAlertview e ModalViewController.

clique em mim

Espero que isto ajude.