[Question] Is it possible to do some kind of block argument overloading? (oslt)
nickynick opened this issue · comments
Hey Justin! I don't know if this is the right place, but I really would like to ask you about one tricky feature I'm struggling with :)
Is it possible to somehow have a block which would accept an argument of different types without complaining? The argument could be id
, but might as well be double
or even struct
(e.g., CGPoint
).
A solution I'm currently using is a macro-based one. I use a macro to wrap scalar/struct arguments in NSValue
, using their encoded types, and then retrieve them back. However, this has a drawback of littering global namespace with defines, which may be undesired.
One thing I noticed is that you may do like this:
void (^block)() = ^{
// do something
};
/**
* no syntax errors here
* (because block invocation is actually done via a function
* accepting variable number of parameters, right?)
*/
block(1);
block([NSObject new]);
Perhaps there's a way to retrieve these passed arguments inside the block? We could assume that we actually know which type we are currently expecting.
Maybe there's another clever approach I'm missing? I know you are an expert on all kinds of crazy things, so perhaps you could give me a clue :) Thanks a lot!
You could use a variable argument list (...
in the declaration and implementation) along with the va_start
, va_arg
, and va_end
macros. However, you definitely have to know how many arguments there are, and their types, if you decide to do that.
This is true, but for that I need at least one named argument. Which I cannot afford given the way things are now :)
From what I read about the blocks implementation, I understood that behind the scenes, the block descriptor itself is the named argument passed to the invocation function. Perhaps this could be a starting point, but simple va_start
can't handle that.
You'd probably have to get deep within libffi to find something that can help if you really don't want any named arguments. I'm afraid I don't know much more than that.
It looks like I got something working :)
@interface Foo : NSObject
@property (nonatomic, assign) char *type;
@property (nonatomic, readonly) void (^bar)();
@end
@implementation Foo
- (void (^)())bar {
if (strcmp(self.type, @encode(id)) == 0) {
return ^(id a) {
NSLog(@"%@", a);
};
} else if (strcmp(self.type, @encode(int)) == 0) {
return ^(int a) {
NSLog(@"%d", a);
};
} else if (strcmp(self.type, @encode(double)) == 0) {
return ^(double a) {
NSLog(@"%lf", a);
};
} else {
return nil;
}
}
@end
Foo *foo = [[Foo alloc] init];
foo.type = @encode(id);
foo.bar(@42);
foo.type = @encode(int);
foo.bar(42);
foo.type = @encode(double);
foo.bar(42.0);
Surprisingly, this seems to work fine! I wonder how valid it is :D
you can't return a block that takes an "id" as parameter
#import <Foundation/Foundation.h>
@interface Foo : NSObject
@property (nonatomic, assign) char *type;
@property (nonatomic, readonly) void (^bar)();
@end
@implementation Foo
- (void (^)())bar {
if (strcmp(self.type, @encode(id)) == 0) {
NSString *star = @"I am a rock star";
return ^(id a) {
NSLog(@"%@", star);
NSLog(@"%@", a);
};
} else if (strcmp(self.type, @encode(int)) == 0) {
return ^(int a) {
NSLog(@"wowo");
NSLog(@"%d", a);
};
} else if (strcmp(self.type, @encode(double)) == 0) {
return ^(double a) {
NSLog(@"huihu");
NSLog(@"%lf", a);
};
} else {
return nil;
}
}
@end
int main(int argc, char *argv[]) {
@autoreleasepool {
Foo *foo = [[Foo alloc] init];
foo.type = @encode(id);
int a = 2;
[foo bar](a);
}
}
the above has a segfault error
@liuyaouestc That's true. The above approach assumes you know the correct argument type before the call.