2011-01-28 7 views
33

Digamos que necesito para comunicarse con una clase que proporciona un protocolo y llama a los métodos de delegado cuando una operación se ha completado, como tan:¿Cómo simplificar la lógica de devolución de llamada con un bloque?

@protocol SomeObjectDelegate 

@required 
- (void)stuffDone:(id)anObject; 
- (void)stuffFailed; 

@end 

@interface SomeObject : NSObject 
{ 
} 
@end 

Ahora, he decidido que mientras podía hacer otra clase Implementar el método delegado stuffDone:, he decidido que prefiero encapsular el proceso en un bloque que está escrito en algún lugar cerca de donde se instancia, se llama, SomeObject, etc. ¿Cómo puedo hacer esto? O en otras palabras, si nos fijamos en this famoso artículo sobre bloques (en la sección Reemplazar devoluciones de llamada); ¿Cómo podría escribir un método en SomeObject que acepte un tipo de completionHandler:?

Respuesta

42

Parece que usted desea comunicarse con una clase existente que está diseñado para tomar una delegar objeto Hay una serie de enfoques, que incluyen:

  1. utilizando una categoría para agregar variantes basadas en bloques de los métodos apropiados;
  2. utiliza una clase derivada para agregar las variantes basadas en bloques; y
  3. escribe una clase que implemente el protocolo y llame a tus bloques.

Aquí hay una manera de hacerlo (3). En primer lugar vamos a suponer que su SomeObject es:

@protocol SomeObjectDelegate 
@required 
- (void)stuffDone:(id)anObject; 
- (void)stuffFailed; 

@end 

@interface SomeObject : NSObject 
{ 
} 

+ (void) testCallback:(id<SomeObjectDelegate>)delegate; 

@end 

@implementation SomeObject 

+ (void) testCallback:(id<SomeObjectDelegate>)delegate 
{ 
    [delegate stuffDone:[NSNumber numberWithInt:42]]; 
    [delegate stuffFailed]; 
} 

@end 

por lo que tienen alguna manera de probar - que tendrá una SomeObject real.

Ahora definir una clase que implementa el protocolo y llama a sus bloques suministrados:

#import "SomeObject.h" 

typedef void (^StuffDoneBlock)(id anObject); 
typedef void (^StuffFailedBlock)(); 

@interface SomeObjectBlockDelegate : NSObject<SomeObjectDelegate> 
{ 
    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 

Esta clase guarda los bloques que entran y los llama en respuesta a las devoluciones de llamada de protocolo. La implementación es sencilla:

@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 

La única cosa que hay que recordar es Block_copy() los bloques cuando se inicializa y para Block_release() más adelante - esto se debe a que los bloques están pila asignados y su objeto pueden sobrevivir a su creador marco de pila; Block_copy() crea una copia en el montón.

Ahora se puede todo, un método basado en el delegado de pasarlo bloques:

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

Puede utilizar esta técnica para envolver los bloques para cualquier protocolo.

ARC Adición

En respuesta al comentario: hacer de este arco compatibles simplemente eliminar las llamadas a Block_copy() dejando asignaciones directas:

stuffDoneCallback = done; 
stuffFailedCallback = fail; 

y retire el método dealloc. También puede cambiar Blockcopy a copy, es decir, stuffDoneCallback = [done copy];, y esto es lo que podría suponer que es necesario al leer la documentación de ARC. Sin embargo, no es como la asignación a una variable fuerte que hace que ARC retenga el valor asignado, y al retener un bloque de pila lo copia al montón. Por lo tanto, el código ARC generado produce los mismos resultados con o sin el copy.

+0

¿Puede explicar esta parte con ARC? stuffDoneCallback = Block_copy (hecho); stuffFailedCallback = Block_copy (error); Me dice que necesita un puente de yeso. – zeiteisen

+0

@zeiteisen - Buena pregunta. Probablemente ya sepa la respuesta, disculpe que nunca vi el comentario. Pero para futuros visitantes, he agregado un apéndice. – CRD

7

Se podría hacer algo como esto:

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; 

A continuación, en el interior SomeObject, se podría hacer:

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

¿Puede explicar por qué deberíamos copiar el bloque? –

1

El siguiente enlace explica cómo las llamadas devueltas usando delegados se pueden reemplazar fácilmente con bloques.

Los ejemplos incluyen UITableview, UIAlertview y ModalViewController.

click me

Espero que esto ayude.

+2

Eche un vistazo a http://stackoverflow.com/help/how-to- answer. Específicamente, esta respuesta podría mejorarse siguiendo esta pauta: "Cita siempre la parte más relevante de un enlace importante, en caso de que el sitio objetivo no esté disponible o permanezca desconectado permanentemente". –

Cuestiones relacionadas