Reputation: 10776
This is different from other "can I check the type of a block" posts on SO, as far as I can tell anyway.
I want to know if, given a block object of unknown signature, I can learn what arguments it accepts prior to invoking?
I have a situation where I have a number of callbacks associated with objects in a dictionary. I want some of those callbacks to expect a different set of arguments. The example here is extremely simplified, but I think it gets the point across.
How can I find out if a block is of a type I previously typedef'd?
//MyClass.m
// I start by declare two block types
typedef void (^callbackWithOneParam)(NSString*);
typedef void (^callbackWithTwoParams)(NSString*, NSObject*);
........
// I create a dictionary mapping objects to callback blocks
self.dict = @{
@"name": "Foo",
@"callback": ^(NSString *aString) {
// do stuff with string
}
}, {
@"name": "Bar",
@"callback": ^(NSString *aString, NSObject *anObject) {
// do stuff with string AND object
}
}
.....
// Later, this method is called.
// It looks up the "name" parameter in our dictionary,
// and invokes the associated callback accordingly.
-(void) invokeCallbackForName:(NSString*)name {
// What is the type of the result of this expression?
[self.dict objectForKey: name]
// I want to say: (pseudocode)
thecallback = [self.dict objectForKey: name];
if (thecallback is of type "callbackWithOneParam") {
thecallback(@"some param")
}
else if (thecallback is of type "callbackWithTwoParams") {
thecallback(@"some param", [[NSObject alloc] init]);
}
}
Upvotes: 9
Views: 2584
Reputation: 16463
Personally, I use the ingenious CTBlockDescription...
CTBlockDescription lets you inspect blocks including arguments and compile time features at runtime.
BOOL(^bk)(BOOL,id) = ^BOOL(BOOL ani, id obj) { return YES; };
[CTBlockDescription.alloc initWithBlock:bk].blockSignature.description;
<NSMethodSignature: 0x253f080>
number of arguments = 3
frame size = 12
is special struct return? NO
return value: -------- -------- -------- --------
type encoding (c) 'c'
flags {isSigned}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 4, size adjust = -3}
memory {offset = 0, size = 1}
argument 0: -------- -------- -------- --------
type encoding (@) '@?'
flags {isObject, isBlock}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 4, size adjust = 0}
memory {offset = 0, size = 4}
argument 1: -------- -------- -------- --------
type encoding (c) 'c'
flags {isSigned}
modifiers {}
frame {offset = 4, offset adjust = 0, size = 4, size adjust = -3}
memory {offset = 0, size = 1}
argument 2: -------- -------- -------- --------
type encoding (@) '@'
flags {isObject}
modifiers {}
frame {offset = 8, offset adjust = 0, size = 4, size adjust = 0}
memory {offset = 0, size = 4}
Gorgeous...
Upvotes: 1
Reputation: 90531
Frankly, if the callbacks have different types, they should be under different keys. Why not use the keys @"callbackWithOneParam"
and @"callbackWithTwoParams"
? To me, that's superior to having a generic "callback" key plus a separate "type" key to tell you how to interpret the callback.
But what this really calls for is to use objects of custom classes instead of dictionaries. You've crossed the boundary where generic objects stop being convenient and start to cause more problems than they solve.
Upvotes: 3
Reputation: 3522
When calling a block you MUST know the type of its arguments. In your case, if the "name" is not sufficient to determine what the arguments should be I would add another key "type" that will tell me. Here is an example:
// Callback dictionary
_callbacks = @{
@{@"name":@"foo", @"type":@(1), @"callback":^(int i) { NSLog(@"%d", i); }},
@{@"name":@"bar", @"type":@(2), @"callback":^(int i, int j) { NSLog(@"%d", i+j); }},
@{@"name":@"zap", @"type":@(3), @"callback":^(int i, int j, int k) { NSLog(@"%d", i+j+k); }},
@{@"name":@"cab", @"type":@(4), @"callback":^(NSString *s) { NSLog(@"%lu",s.length); }},
@{@"name":@"fog", @"type":@(5), @"callback":^(void) { NSLog(@"I can't see"); }}
}
-(void) invokeCallbackForName:(NSString*)name withArguments:(NSArray*)args {
NSDictionary *info = _callbacks[name];
if (info != nil) {
id block = info[@"callback"];
int type = [info[@"type"] intValue];
switch (type) {
case 1: {
int arg1 = [args[0] intValue];
((void(^)(int)) block)(arg1);
break;
}
case 2: {
int arg1 = [args[0] intValue];
int arg2 = [args[1] intValue];
((void(^)(int,int)) block)(arg1,arg2);
break;
}
case 3: {
int arg1 = [args[0] intValue];
int arg2 = [args[1] intValue];
int arg3 = [args[2] intValue];
((void(^)(int,int,int)) block)(arg1,arg2,arg3);
break;
}
case 5: {
NSString *arg1 = [args[0] intValue];
((void(^)(NSString*)) block)(arg1);
break;
}
default:
[NSExceptien raise:NSInvalidArgumentException format:@"Unsupported callback type"];
}
}
}
Note that it's imperative that you cast the block to the correct type otherwise you will probably have your program crash. That's because the block depends on the compiler to put the arguments on the stack in the correct order and allowing for any return type.
Upvotes: 0
Reputation: 61
just check if the name is "Foo" or "Bar"? will probably be just as much effort as checking the functions parameters, which i have a feeling isnt possible without having it in some form of class and going
if ([myObject class] == [MyClass class])
Upvotes: -1