Блок многоадресной рассылки: как обобщить

Цель

У меня есть класс с различными свойствами, которые можно использовать для подключения блока для получения определенных событий.

@interface SomeClass

@property (copy, nonatomic) void (^handler)(int arg1, int arg2);

@end

В клиентском коде я хотел бы динамически добавлять / удалять блоки обработчиков к этому свойству, аналогично MulticastDelegate в C#.

self.logger = ^(int arg1, int arg2){
    NSLog(@"arg1 = %d, arg2 = %d", arg1, arg2);
};

void (^doSomething)(int, int) = ^(int arg1, int arg2){
    if (arg1 == 42) {
        // Do something.
    }
};

Например, я хотел бы подключить logger в -(id)init, но только использовать doSomething в то время как определенный метод работает. В то время как doSomething подключен, logger должен еще бежать.

Текущая реализация

Чтобы сохранить блоки, я подумал об использовании NSMutableArray который хранит копии блоков и передает событие всем зарегистрированным блокам (шаблон наблюдателя).

- (id) init

self.handlerBlocks = [NSMutableArray array];
__weak typeof(self) weakSelf = self;
self.object.handler = ^(int x, int y){
    typeof(self) strongSelf = weakSelf;
    if (!strongSelf) {
        return;
    }
    for (void (^item)(int x, int y) in strongSelf.handlerBlocks) {
        item(x, y);
    }
};

[self.handlerBlocks addObject:[self.logger copy]];

- (недействительно)someOtherMethod

void (^doSomething)(int, int) = [^(int arg1, int arg2){
    if (arg1 == 42) {
        // Do something.
    }
} copy];
[self.handlerBlocks addObject:doSomething copy];
// Do something.
[self.handlerBlocks removeObject:doSomething];

Открытые вопросы

Можно ли обобщить метод на блоки с любым количеством / типом аргументов? Чтобы я мог использовать это так:

MulticastBlock *b = [[MulticastBlock alloc] init];
self.object.handler = b;

[b addBlock:self.logger];

Проблема здесь в том, что тип self.object.handler является void (^)(int, int), Следовательно, MulticastBlock нужно будет имитировать блок, перенаправляя любые вызовы, которые он получает, в массив.

Могут ли методы, описанные здесь, использоваться?

Может быть, перехватывать все вызовы, копировать их для каждого элемента массива и назначать новые цели вызова?

1 ответ

По ссылке, которую вы дали на mikeash.com, вы увидите, что сделать это в коде - это задача, а не то, что нужно включать в рабочий код. По тем же причинам C# работает, потому что это обеспечивается средой выполнения, вы не могли легко написать это сами на C#. Даже параметрический полиморфизм здесь вам не поможет, это не даст вам вызов блока с различным количеством аргументов.

Что вам нужно, так это "параметрический полиморфизм" путем расширения строки... т.е. макросы.

Вот пример файла "MulticastBlock.h":

#define MULTICAST(name, typelist, arglist) \
\
@interface name : NSObject \
\
@property (readonly) void (^block)typelist; \
\
- (id) addBlock:(void (^)typelist)aBlock; \
\
- (void) removeBlock:(id)token; \
\
@end

MULTICAST(MulticastBlock, (int arg1, int arg2), (arg1, arg2))
MULTICAST(MulticastBlock2, (NSString *arg1, NSString *arg2), (arg1, arg2))

#undef MULTICAST

Это определяет макрос, который расширяется до @interface, использует его дважды, затем удаляет макрос, так как он больше не нужен.

Реализация следует вашему коду и аналогичным образом выполняется с помощью макроса - она ​​использует arglist аргумент макроса для вызова в цикле, я просто включил его здесь для согласованности, хотя он не используется.

Единственное существенное изменение, которое я внес в ваш код, - это использование NSMutableDictionary с автоматически сгенерированным ключом (просто увеличивающееся число) - ключ возвращается addBlock: и приняты removeBlock: и позволяет избежать проблем с копируемыми блоками (два блока равны, только если они являются одним и тем же блоком)

Не совсем то, что вы хотели бы, но это работает.

добавление

Хорошо, не было понятно, как это использовать, вот мой тестовый код, который должен объяснить все:

MulticastBlock *multicast = MulticastBlock.new;

id tokenAdd = [multicast addBlock:^(int arg1, int arg2) {
   NSLog(@"%d + %d -> %d", arg1, arg2, arg1 + arg2);
}];

multicast.block(3, 4);

id tokenMul = [multicast addBlock:^(int arg1, int arg2) {
   NSLog(@"%d * %d -> %d", arg1, arg2, arg1 * arg2);
}];

multicast.block(4, 5);

[multicast removeBlock:tokenAdd];

multicast.block(5, 6);

[multicast removeBlock:tokenMul];

multicast.block(6, 7);

MulticastBlock2 *two = MulticastBlock2.new;

[two addBlock:^(NSString *arg1, NSString *arg2) {
   NSLog(@"%@ | %@", arg1, arg2);
}];

two.block(@"asda", @"tesco");
Другие вопросы по тегам