2011-12-27 11 views
9

Sto cercando di creare una categoria per sostituire i metodi delegati con i blocchi callback per molte delle semplici API iOS. Simile al blocco sendAsyc su NSURLConnection. Ci sono 2 tecniche che sono senza perdite e sembrano funzionare bene. Quali sono i pro/contro su ciascuno? C'è un modo migliore?La migliore tecnica per sostituire i metodi delegati con i blocchi

Opzione 1. Utilizzare una categoria per implementare il metodo di callback del delegato su NSObject con il blocco callback esterno con ambito.

// 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 

Questo aggiunge un sacco di metodi sul NSObject e sembra che potrebbe causare problemi con qualsiasi altra classe cercando di utilizzare il metodo delegato standard. Ma mantiene il blocco in vita con l'oggetto e restituisce il callback senza perdite che ho trovato.


Opzione 2. Creare una classe leggero per contenere il blocco, dynamicly associarlo con la classe in modo che rimarrà nel mucchio e rimuoverlo quando il callback è completo.

// 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 

Mi piace che questo non aggiunga nulla sopra NSObject e le cose sono un po 'più separate. Si collega all'istanza tramite l'indirizzo della funzione.

+0

Opzione 3: sottoclasse 'UIAlertView'. –

+0

Giusto. Lavori di sottoclasse. Ma sarà ingombra e avrà un codice meno riutilizzabile quando sto sottoclassi ogni API Apple per aggiungere una chiamata al metodo. Inoltre sto mettendo tutte queste API in una classe, quindi è facile da importare e l'uso di categorie consente alla chiamata al metodo di essere più pulita e vicina alle API di Apple. Se si finisce per essere un bel modo generico di trattenere e restituire il blocco, allora quel codice può essere riutilizzato con piccole modifiche ogni volta che devo aggiungere un altro metodo di blocco asincrono a un'API di Apple. – puppybits

+0

Ok, capisco. È piuttosto un'impresa. Stavo solo provando a salvarti dal fare il giro con il runtime. –

risposta

2

Ho avuto un problema simile e ha scelto l'opzione 2, ma con le 2 piccole aggiunte:

  1. esplicitamente che segna il delegato attua in questo modo:

    @interface __DelegateBlock:NSObject <BlocksDelegate> 
    
  2. Controllare per garantire la callback non è nulla prima di chiamare:

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

Sì, dopo alcuni giorni di stufatura finita. L'opzione 2 è davvero molto meglio. I tuoi consigli sono grandi per ripulirlo un po 'meglio. Grazie. – puppybits

0

Ecco quello che ho fatto:

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 
Problemi correlati