Подтвердить что ты не робот

Как упростить логику обратного вызова с помощью блока?

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

@protocol SomeObjectDelegate

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

@end

@interface SomeObject : NSObject
{
}
@end

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

4b9b3361

Ответ 1

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

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

Вот один из способов сделать (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.

Ответ 2

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

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);
}

Ответ 3

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

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

нажмите меня

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