Goal
I have a class with various properties that can be used to plug in a block to receive certain events.
@interface SomeClass
@property (copy, nonatomic) void (^handler)(int arg1, int arg2);
@end
In the client code, I would like to dynamically add / remove handler blocks to this property, similar to a MulticastDelegate in 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.
}
};
For example, I would like to plug in logger
in -(id)init
, but only use doSomething
while a certain method is running. While doSomething
is plugged in, logger
should still run.
Current implementation
To maintain the blocks, I thought about using an NSMutableArray
that stores copies of the blocks and broadcasts the event to all registered blocks (observer pattern).
- (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]];
- (void)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];
Open questions
Can the method be generalized to blocks with any argument count / types? So that I could use it like this:
MulticastBlock *b = [[MulticastBlock alloc] init];
self.object.handler = b;
[b addBlock:self.logger];
The problem here is that the type of self.object.handler
is void (^)(int, int)
. Therefore, MulticastBlock
would need to mimic a block, forwarding any invocations it receives to the array.
Could the techniques described here by used?
Maybe intercepting all invocations, copying them for every array element and assigning new invocation targets?
From the link you gave to mikeash.com you'll see doing this in code is a challenge, and not something to include in production code. For similar reasons C# stuff works because it is provided by the runtime, you couldn't easily write it yourself in C#. Even parametric polymorphism won't help you here, that won't get you the block call with a varying number of arguments.
What you need is "parametric polymorphism" by string expansion... i.e. macros.
Here is a sample "MulticastBlock.h" file:
This defines a macro which expands out to an
@interface
, uses it twice, then removes the macro as its no longer needed.The implementation follows your code and is done likewise with a macro - it uses the
arglist
macro argument for the call in the loop, I just include it here for consistency though it isn't used.The only significant change I made to your code was using an
NSMutableDictionary
with a auto-generated key (just an increasing number) - the key is returned byaddBlock:
and accepted byremoveBlock:
and avoids any issues with blocks being copied (two blocks are only equal if they are the same block)Not exactly what you'd like but it works.
Addendum
OK, it wasn't clear how to use this, here is my test code which should explain all: