2011-12-27 8 views
9

Estoy buscando crear una categoría para reemplazar los métodos de delegado con bloques de devolución de llamada para muchas de las API de iOS simples. Similar al bloque sendAsyc en NSURLConnection. Hay 2 técnicas que no tienen fugas y parecen funcionar bien. ¿Cuáles son los pros/contra de cada uno? ¿Hay una mejor manera?Mejor técnica para reemplazar métodos de delegado con bloques

Opción 1. Use una categoría para implementar el método de devolución de llamada del delegado en NSObject con el bloque de devolución de llamada externa con ámbito.

// Add category on NSObject to respond to the delegate 
@interface NSObject(BlocksDelegate) 
- (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex; 
@end 

@implementation NSObject(BlocksDelegate) 
- (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex 
{ 
    // Self is scoped to the block that was copied 
    void(^callback)(NSInteger) = (id)self; 
    // Call the callback passed if 
    callback(buttonIndex); 
    [self release]; 
} 
@end 

// Alert View Category 
@implementation UIAlertView (BlocksDelegate) 
+ (id) alertWithTitle:(NSString*)title 
       message:(NSString*)message 
     clickedBlock:(void(^)(NSInteger))buttonIndexClickedBlock 
    cancelButtonTitle:(NSString*)cancelButtonTitle 
    otherButtonTitles:(NSString*)otherButtonTitles 
{ 
    // Copy block passed in to the Heap and will stay alive with the UIAlertView 
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title 
                message:message 
                delegate:[buttonIndexClickedBlock copy] 
              cancelButtonTitle:cancelButtonTitle 
              otherButtonTitles:otherButtonTitles, nil]; 

    // Display the alert 
    [alert show]; 

    // Autorelease the alert 
    return [alert autorelease]; 
} 

@end 

Esto añade una gran cantidad de métodos en el NSObject y parece que podría causar problemas con cualquier otra clase tratando de utilizar el método de delegado estándar. Pero mantiene el bloque vivo con el objeto y devuelve la devolución de llamada sin ninguna filtración que he encontrado.


Opción 2. Crear una clase de peso ligero para contener el bloque, dynamicly asociarlo con la clase para que permanezca en el montón y retirarlo cuando la devolución de llamada se ha completado.

// Generic Block Delegate 
@interface __DelegateBlock:NSObject 
typedef void (^HeapBlock)(NSInteger); 
@property (nonatomic, copy) HeapBlock callbackBlock; 
@end 

@implementation __DelegateBlock 
@synthesize callbackBlock; 
- (id) initWithBlock:(void(^)(NSInteger))callback 
{ 
    // Init and copy Callback Block to the heap (@see accessor) 
    if (self = [super init]) 
     [self setCallbackBlock:callback]; 
    return [self autorelease]; 
} 
- (void) dealloc 
{ 
    // Release the block 
    [callbackBlock release], callbackBlock = nil;  
    [super dealloc]; 
} 
- (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex 
{ 
    // Return the result to the callback 
    callbackBlock(buttonIndex); 

    // Detach the block delegate, will decrement retain count 
    SEL key = @selector(alertWithTitle:message:clickedBlock:cancelButtonTitle:otherButtonTitles:); 
    objc_setAssociatedObject(alertView, key, nil, OBJC_ASSOCIATION_RETAIN); 
    key = nil; 

    // Release the Alert 
    [alertView release]; 
} 
@end 

@implementation UIAlertView (BlocksDelegate) 
+ (id) alertWithTitle:(NSString*)title 
       message:(NSString*)message 
     clickedBlock:(void(^)(NSInteger))buttonIndexClickedBlock 
    cancelButtonTitle:(NSString*)cancelButtonTitle 
    otherButtonTitles:(NSString*)otherButtonTitles 
{ 
    // Create class to hold delegatee and copy block to heap 
    DelegateBlock *delegatee = [[__DelegateBlock alloc] initWithBlock:buttonIndexClickedBlock]; 
    [[delegatee retain] autorelease]; 
    // Create delegater 
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title 
                message:message 
                delegate:delegatee 
              cancelButtonTitle:cancelButtonTitle 
              otherButtonTitles:otherButtonTitles, nil]; 

    // Attach the Delegate Block class to the Alert View, increase the retain count 
    objc_setAssociatedObject(alert, _cmd, delegatee, OBJC_ASSOCIATION_RETAIN); 

    // Display the alert 
    [alert show]; 
    return alert; 
} 

@end 

me gusta que esto no aporta nada en la parte superior de NSObject y las cosas son un poco más separados. Se adjunta a la instancia a través de la dirección de la función.

+0

Opción 3: Subclase 'UIAlertView'. –

+0

Derecha. Trabajos de subclases Pero estará desordenado y tendrá menos código reutilizable cuando estoy subclasificando cada API de Apple para agregar una llamada a un método. Además, estoy colocando todas estas API en una sola clase para que sea fácil de importar y usar categorías, lo que permite que la llamada al método sea más limpia y más cercana a las API de Apple. Si termina siendo una buena manera genérica de mantener y devolver el bloque, entonces ese código puede reutilizarse con pequeños cambios cada vez que necesito agregar un método de bloque asíncrono a una API de Apple. – puppybits

+0

Ok, ya veo. Eso es toda una empresa. Solo estaba tratando de salvarte de perder el tiempo de ejecución. –

Respuesta

2

tuve un problema similar y elija su opción 2, pero con las 2 pequeñas adiciones:

  1. explícitamente marcando el delegado implementa como esto:

    @interface __DelegateBlock:NSObject <BlocksDelegate> 
    
  2. Comprobar para asegurar la la devolución de llamada no es nula antes de llamar:

    if (callbackBlock != nil) { 
        callbackBlock(buttonIndex); 
    } 
    
+0

Sí, después de unos días de guisarlo. La opción 2 realmente es mucho mejor. Sus consejos son geniales para limpiarlo un poco mejor. Gracias. – puppybits

0

Aquí es lo que hice:

typedef void(^EmptyBlockType)(); 

@interface YUYesNoListener : NSObject <UIAlertViewDelegate> 

@property (nonatomic, retain) EmptyBlockType yesBlock; 
@property (nonatomic, retain) EmptyBlockType noBlock; 

+ (void) yesNoWithTitle:(NSString*)title message:(NSString*)message yesBlock:(EmptyBlockType)yesBlock noBlock:(EmptyBlockType)noBlock; 

@end 

@implementation YUYesNoListener 

@synthesize yesBlock = _yesBlock; 
@synthesize noBlock = _noBlock; 

- (id) initWithYesBlock:(EmptyBlockType)yesBlock noBlock:(EmptyBlockType)noBlock 
{ 
    self = [super init]; 
    if (self) 
    { 
     self.yesBlock = [[yesBlock copy] autorelease]; 
     self.noBlock = [[noBlock copy] autorelease]; 
    } 
    return self; 
} 

- (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex 
{ 
    if (buttonIndex == 0 && self.noBlock) 
     self.noBlock(); 
    else if (buttonIndex == 1 && self.yesBlock) 
     self.yesBlock(); 

    [_yesBlock release]; 
    [_noBlock release]; 
    [alertView release]; 
    [self release]; 
} 

- (void) alertViewCancel:(UIAlertView *)alertView 
{ 
    if (self.noBlock) 
     self.noBlock(); 
    [_yesBlock release]; 
    [_noBlock release]; 
    [alertView release]; 
    [self release]; 
} 

+ (void) yesNoWithTitle:(NSString*)title message:(NSString*)message yesBlock:(EmptyBlockType)yesBlock noBlock:(EmptyBlockType)noBlock 
{ 
    YUYesNoListener* yesNoListener = [[YUYesNoListener alloc] initWithYesBlock:yesBlock noBlock:noBlock]; 
    [[[UIAlertView alloc] initWithTitle:title message:message delegate:yesNoListener cancelButtonTitle:@"No" otherButtonTitles:@"Yes", nil] show]; 
} 

@end 
Cuestiones relacionadas