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:
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.