Skip to content

Instantly share code, notes, and snippets.

@enigmaticape
Created November 17, 2012 16:30
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save enigmaticape/4097340 to your computer and use it in GitHub Desktop.
Save enigmaticape/4097340 to your computer and use it in GitHub Desktop.
Wrapping NSInvocation for fun and profit, for values of profit that include a better peformSelector
#import <Foundation/Foundation.h>
@interface Invoker : NSObject
+ ( NSValue * ) invoke:( SEL ) selector onTarget:( id ) target, ...;
@end
#import "Invoker.h"
@implementation Invoker
+ ( NSValue * ) invoke:( SEL ) selector onTarget:( id ) target, ... {
/* get set up to use a variadic argument list */
va_list args;
va_start(args, target);
/* Do the standard NSInvocation setup */
NSMethodSignature * signature
= [target methodSignatureForSelector:selector];
NSInvocation * invocation
= [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget: target];
[invocation setSelector:selector];
/* Now then, here starts the fun part,
get a count of how many arguments to expect, information
which is encapsulated by the method signature.
*/
NSUInteger arg_count = [signature numberOfArguments];
for( NSInteger i = 0; i < arg_count - 2; i++ ) {
/*
get an id value from the argument list.
*/
id arg = va_arg(args, id);
/*
if it is an NSValue, or subclass thereof, we have some extra
work to do before we can pass it on to the invocation
*/
if( [arg isKindOfClass:[NSValue class]] ) {
NSUInteger arg_size;
/*
NSGetSizeAndAlignment will give us a size in bytes
based on the type information that the runtime has stored,
which is accessed (in NSValue's case) by calling
[NSValue objCtype], 4 for an it, 1 for a char and so on.
*/
NSGetSizeAndAlignment([arg objCType], &arg_size, NULL);
/*
We can't (AFAIK) directly access NSValue's bytes,
so we'll need a temporary buffer to read the value into.
It's possible that we could simply use a large buffer and
rely on NSInvocation to know how much to copy, thus obviating
the need for multiple malloc()s, but I haven't tested that yet
*/
void * arg_buffer = malloc(arg_size);
/* copy the value, obvs
*/
[arg getValue:arg_buffer];
/*
Now we pass the buffer to NSInvocation, which copies it.
NB that we start at index 2, because index 0 and 1 are
the id and SEL of the targeted method
*/
[invocation setArgument:arg_buffer atIndex: 2 + i ];
free(arg_buffer);
}
else {
/*
If we have a non NSValue argument, just copy it
*/
[invocation setArgument:&arg atIndex: 2 + i];
}
}
/*
indicate that we are done with the variadic argument list
*/
va_end(args);
/*
do the bidniss
*/
[invocation invoke];
/*
Of course now, we have to get the value back out.
There are a couple f ways to do thsi, let's stick
with NSValue, since we're on such close terms.
*/
NSValue * ret_val = nil;
NSUInteger ret_size = [signature methodReturnLength];
if( ret_size > 0 ) {
void * ret_buffer = malloc( ret_size );
[invocation getReturnValue:ret_buffer];
ret_val = [NSValue valueWithBytes:ret_buffer
objCType:[signature methodReturnType]];
free(ret_buffer);
}
return ret_val;
}
@end
#import <Foundation/Foundation.h>
#import "TestObj.h"
#import "Invoker.h"
#import "NSObject+invokeSelector.h"
int main(int argc, const char * argv[])
{
@autoreleasepool {
TestObj * testobj = [[TestObj alloc] init];
SEL selector = @selector(printString:andInt:andIncrement:);
float aFloat = 123.456;
int anInt = 42;
NSString * aString = @"The answer is";
NSValue * result;
float floatresult;
result = [Invoker invoke:selector onTarget:testobj,
aString,
[NSNumber numberWithInt:anInt],
[NSNumber numberWithFloat:aFloat]
];
[result getValue: &floatresult];
NSLog(@"%f", floatresult);
/* And now with the NSObject category */
result = [testobj invokeSelector:selector,
aString,
[NSNumber numberWithInt:anInt],
[NSNumber numberWithFloat:aFloat]
];
[result getValue: &floatresult];
NSLog(@"%f", floatresult);
// Really, still no ARC, tsk tsk, Steve.
[testobj release];
}
return 0;
}
#import <Foundation/Foundation.h>
@interface NSObject (invokeSelector)
- (NSValue *) invokeSelector:(SEL)selector, ...;
@end
#import "NSObject+invokeSelector.h"
@implementation NSObject (invokeSelector)
- (NSValue *) invokeSelector:(SEL)selector, ... {
va_list args;
va_start(args, selector);
NSMethodSignature * signature
= [self methodSignatureForSelector:selector];
NSInvocation * invocation
= [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget:self];
[invocation setSelector:selector];
NSUInteger arg_count = [signature numberOfArguments];
for( NSInteger i = 0; i < arg_count - 2; i++ ) {
id arg = va_arg(args, id);
if( [arg isKindOfClass:[NSValue class]] ) {
NSUInteger arg_size;
NSGetSizeAndAlignment([arg objCType], &arg_size, NULL);
void * arg_buffer = malloc(arg_size);
[arg getValue:arg_buffer];
[invocation setArgument:arg_buffer atIndex: 2 + i ];
free(arg_buffer);
}
else {
[invocation setArgument:&arg atIndex: 2 + i];
}
}
va_end(args);
[invocation invoke];
NSValue * ret_val = nil;
NSUInteger ret_size = [signature methodReturnLength];
if( ret_size > 0 ) {
void * ret_buffer = malloc( ret_size );
[invocation getReturnValue:ret_buffer];
ret_val = [NSValue valueWithBytes:ret_buffer
objCType:[signature methodReturnType]];
free(ret_buffer);
}
return ret_val;
}
@end
#import <Foundation/Foundation.h>
@interface NSObject (invokeSelector)
- (NSValue *) invokeSelector:(SEL)selector, ...;
@end
#import "NSObject+invokeSelector.h"
@implementation NSObject (invokeSelector)
- (NSValue *) invokeSelector:(SEL)selector, ... {
va_list args;
va_start(args, selector);
NSMethodSignature * signature
= [self methodSignatureForSelector:selector];
NSInvocation * invocation
= [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget:self];
[invocation setSelector:selector];
NSUInteger arg_count = [signature numberOfArguments];
for( NSInteger i = 0; i < arg_count - 2; i++ ) {
id arg = va_arg(args, id);
if( [arg isKindOfClass:[NSValue class]] ) {
NSUInteger arg_size;
NSGetSizeAndAlignment([arg objCType], &arg_size, NULL);
void * arg_buffer = malloc(arg_size);
[arg getValue:arg_buffer];
[invocation setArgument:arg_buffer atIndex: 2 + i ];
free(arg_buffer);
}
else {
[invocation setArgument:&arg atIndex: 2 + i];
}
}
va_end(args);
[invocation invoke];
NSValue * ret_val = nil;
NSUInteger ret_size = [signature methodReturnLength];
if( ret_size > 0 ) {
void * ret_buffer = malloc( ret_size );
[invocation getReturnValue:ret_buffer];
ret_val = [NSValue valueWithBytes:ret_buffer
objCType:[signature methodReturnType]];
free(ret_buffer);
}
return ret_val;
}
@end
#import <Foundation/Foundation.h>
@interface TestObj : NSObject
-(float) printString:(NSString*) aString
andInt:(int) anInt
andIncrement:(float) aFloat;
@end
#import "TestObj.h"
@implementation TestObj
-(float) printString:(NSString*) aString
andInt:(int) anInt
andIncrement:(float) aFloat
{
NSLog(@"%@ : %i : (%f)", aString, anInt, aFloat);
return aFloat + 1.0;
}
@end
@enigmaticape
Copy link
Author

From a series on things that performSelector probably ought to do but doesn't, and how to get around that in some ways more exciting than using an array. At the Enigmatic Ape Blog http://www.enigmaticape.com/blog/wrapping-nsinvocation-for-fun-and-profit/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment