Блок многоадресной рассылки: как обобщить
Цель
У меня есть класс с различными свойствами, которые можно использовать для подключения блока для получения определенных событий.
@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");