2011-12-27 2 views
9

Я ищу, чтобы создать категорию, чтобы заменить методы делегирования блоками обратных вызовов для множества простых API-интерфейсов iOS. Подобно блоку sendAsyc для NSURLConnection. Есть 2 метода, которые не содержат утечек и, похоже, работают нормально. Каковы плюсы и минусы каждого из них? Есть ли способ лучше?Лучшая техника для замены методов делегатов с помощью блоков

Вариант 1. Используйте категорию для реализации метода обратного вызова делегата в NSObject с использованием внешнего блока обратного вызова.

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

Это добавляет много методов на NSObject и кажется, что это может вызвать проблемы с любым другим классом пытается использовать стандартный метод делегата. Но он сохраняет блок живым с объектом и возвращает обратный вызов без каких-либо утечек, которые я нашел.


Вариант 2. Создание класса облегченного содержит блок, динамически связать его с классом, так он будет оставаться в куче и удалить его, когда обратный вызов завершен.

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

Мне нравится, что это ничего не добавляет к NSObject, и вещи немного более разделены. Он прикрепляется к экземпляру через адрес функции.

+0

Вариант 3: Подкласс 'UIAlertView'. –

+0

Справа. Работа подкласса. Но он будет загроможден и имеет меньший код повторного использования, когда я подклассифицирую каждый API-интерфейс Apple, чтобы добавить один вызов метода. Кроме того, я помещаю все эти API в один класс, поэтому легко импортировать и использовать категории, чтобы вызов метода был более чистым и ближе к API-интерфейсам Apple. Если заканчивается хороший общий способ удержания и возврата блока, тогда этот код может быть повторно использован с небольшими изменениями, когда мне нужно добавить еще один асинхронный блочный метод к API-интерфейсу Apple. – puppybits

+0

Хорошо, я вижу. Это довольно серьезная задача. Я просто пытался спасти вас от врезания во время работы. –

ответ

2

У меня была аналогичная проблема, и выбрал свой вариант 2, но с 2 небольшими дополнениями:

  1. Явное маркировки делегата он реализует так:

    @interface __DelegateBlock:NSObject <BlocksDelegate> 
    
  2. Проверьте, чтобы обеспечить обратный вызов не равен нулю перед вызовом:

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

Да, после нескольких дней тушения. Вариант 2 действительно намного лучше. Ваши советы хороши, чтобы немного почистить его. Благодарю. – puppybits

0

Вот что я сделал:

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 
Смежные вопросы