Как упростить логику обратного вызова с помощью блока? - PullRequest
33 голосов
/ 28 января 2011

Допустим, мне нужно связаться с классом, который предоставляет протокол и вызывает методы делегата после завершения операции, как показано ниже:

@protocol SomeObjectDelegate

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

@end

@interface SomeObject : NSObject
{
}
@end

Теперь я решил, что хотя я мог бы заставить другой класс реализовать метод делегата stuffDone:, я решил, что лучше заключить процесс в блок, который записан где-то рядом куда SomeObject создается, вызывается и т. д. Как я могу это сделать? Или, другими словами, если вы посмотрите на эту знаменитую статью о блоках (в разделе «Заменить обратные вызовы»); Как я могу написать метод в SomeObject, который принимает completionHandler: сортов?

Ответы [ 3 ]

42 голосов
/ 30 января 2011

Звучит так, как будто вы хотите общаться с существующим классом, который предназначен для приема объекта делегата. Существует ряд подходов, в том числе:

  1. использование категории для добавления блочных вариантов соответствующих методов;
  2. использовать производный класс для добавления блочных вариантов; и
  3. написать класс, который реализует протокол и вызывает ваши блоки.

Вот один из способов сделать это (3). Сначала давайте предположим, что ваш SomeObject:

@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

так что у нас есть способ проверить - у вас будет настоящий SomeObject.

Теперь определите класс, который реализует протокол и вызывает ваши предоставленные блоки:

#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

Этот класс сохраняет блоки, которые вы передаете, и вызывает их в ответ на обратные вызовы протокола. Реализация проста:

@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

Единственное, что вам нужно запомнить, - это Block_copy () блоков при инициализации и Block_release () их позже - это потому, что блоки распределены в стеке, и ваш объект может пережить создаваемый кадр стека; Block_copy () создает копию в куче.

Теперь вы можете передавать все методы на основе делегатов, передавая ему блоки:

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

Вы можете использовать эту технику для упаковки блоков для любого протокола.

Приложение ARC

В ответ на комментарий: для обеспечения совместимости ARC просто удалите вызовы на Block_copy(), оставив прямые назначения:

stuffDoneCallback = done;
stuffFailedCallback = fail;

и удалите метод dealloc. Вы также можете изменить Blockcopy на copy, т. Е. stuffDoneCallback = [done copy];, и это то, что вы, возможно, считаете необходимым при чтении документации ARC. Однако это не так, как присваивание сильной переменной, которая заставляет ARC сохранять назначенное значение - и сохранение блока стека копирует его в кучу. Поэтому сгенерированный код ARC дает одинаковые результаты с copy.

или без него.
7 голосов
/ 28 января 2011

Вы можете сделать что-то вроде этого:

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;

Тогда внутри SomeObject вы можете сделать:

if ([self hadError]) {
  callback([self error]);
} else {
  callback(nil);
}
1 голос
/ 19 августа 2013

Ссылка ниже объясняет, как обратные вызовы с использованием делегатов могут быть легко заменены блоками.

Примеры включают в себя UITableview, UIAlertview и ModalViewController.

нажмите меня

Надеюсь, это поможет.

...