Skip to content

Instantly share code, notes, and snippets.

@enigmaticape
Created November 9, 2012 14:26
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save enigmaticape/4045986 to your computer and use it in GitHub Desktop.
Save enigmaticape/4045986 to your computer and use it in GitHub Desktop.
You want to use NSObject performSelector with multiple parameters, but you can't ?
#import <Foundation/Foundation.h>
#import <Foundation/Foundation.h>
#import <objc/message.h>
@interface AmazingClass : NSObject
- ( void ) doMultipleAmazingStuff;
@end
@implementation AmazingClass
/*
Let's say we have the following amazingly cool
methods on a class, and that for whatever reason,
we need to call them indirectly, perhaps using
[NSObject perFormSelector: ... ]
*/
- ( id ) methodWithOneParam:( id ) theParam {
// Do amazing stuff
return @"Srsly, Amazing!";
}
- ( id ) methodWithFirst:( id ) firstParam
andSecond:( id ) secondParam
{
// Do doubly amazing stuff
return @"Even Amazinger";
}
- ( id ) methodWithFirst:( id ) firstParam
andSecond:( id ) secondParam
andThird:( id ) thirdParam
{
// Do thricely amazing stuff that will certainly rock.
return @"MOAR AMAZINGER-ER!";
}
/* How do we go about calling them ? */
- ( void ) doMultipleAmazingStuff {
id first = nil;
id second = nil;
id third = nil;
id result = nil;
// easy peasy to do the first one.
SEL singleParamSelector = @selector(methodWithOneParam:);
result = [self performSelector:singleParamSelector // https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Protocols/NSObject_Protocol/Reference/NSObject.html
withObject:first];
NSLog(@"%@", result);
/*
Let's get a bit trickier and get the selector signature
from a string this tine
*/
SEL doubleParamSelector
= NSSelectorFromString(@"methodWithFirst:andSecond:");
result = [self performSelector: doubleParamSelector
withObject: first
withObject: second];
NSLog(@"%@", result);
/*
Unfortunately, that's where the magic stops. If you google
around this subject lots of people suggest that to go
further you need an NSInvocation, shudder, the horror.
This is how Apple suggests this should look. Actually,
this is slightly *nicer* than the sample code.
*/
NSInvocation * invocation; // https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/DistrObjects/Tasks/invocations.html#//apple_ref/doc/uid/20000744-CJBBACJH
NSMethodSignature * methSig; // http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSMethodSignature_Class/Reference/Reference.html
SEL triParamSelector;
/*
We need both a selector and a method signature,
they are different. A method signature contains
information about the number and types of arguments
a method takes, whereas a selector is just a name.
*/
triParamSelector = @selector(methodWithFirst:andSecond:andThird:);
methSig = [self methodSignatureForSelector: triParamSelector];
invocation = [NSInvocation invocationWithMethodSignature: methSig];
/*
Set the target (the object to invoke the
selector on) and the selector to invoke
*/
[invocation setSelector: triParamSelector];
[invocation setTarget: self];
/*
Now we have to set up the arguments. Note that we
have to start from 2, because there are two 'hidden'
arguments, which we'll get to shortly
*/
[invocation setArgument: &first atIndex: 2];
[invocation setArgument: &second atIndex: 3];
[invocation setArgument: &third atIndex: 4];
/*
Now make the call and get the returned value.
*/
[invocation invoke];
[invocation getReturnValue: &result];
NSLog(@"NSInvocation : %@", result);
/*
And that is how you do that.
Or, you could just do either this ...
*/
SEL betterWaySelector
= NSSelectorFromString(@"methodWithFirst:andSecond:andThird:");
IMP methodImplementation // https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSObject_Class/Reference/Reference.html
= [self methodForSelector: betterWaySelector];
result = methodImplementation( self,
betterWaySelector,
first,
second,
third );
NSLog(@"methodForSelector : %@", result);
/* which is 11 lines shorter, or this ... */
SEL evenBetterWay
= NSSelectorFromString(@"methodWithFirst:andSecond:andThird:");
result = objc_msgSend( self,
evenBetterWay,
first,
second,
third );
NSLog(@"objc_msgSend : %@", result);
/*
which is 12 lines shorter
*/
/* with the caveat that you're really
not supposed to, to the extent that Apple
have included a note in the documentation
specifically to tell you not to do it.
OTOH, there is no such note for using
methodForSelector, and the docs say that
you would use it if you wanted to avoid
the overhead of dynamic binding and
messaging.
*/
}
@end
@interface BoringClass : NSObject
- ( void ) doBoringStuff;
@end
@implementation BoringClass
/*
Of course, there is aways the boring,
conventional old way, but this requires
you to know beforehand that you will
will be calling code via peformSelector
so that al your code is set up the same way,
or to refactor any code that calls your
target if you surprise yourself
*/
- ( id ) boringMethod:( NSDictionary *) args {
id first = [args objectForKey:@"first" ];
id second = [args objectForKey:@"second"];
id third = [args objectForKey:@"third" ];
return @"Whatever";
}
- ( void ) doBoringStuff {
id first = nil;
id second = nil;
id third = nil;
id result = nil;
SEL yawn = @selector(boringMethod:);
NSDictionary * args
= [NSDictionary dictionaryWithObjectsAndKeys:
first, @"first",
second, @"second",
third, @"third", nil];
result = [self performSelector: yawn
withObject: args];
NSLog(@"booooorrrrring : %@", result);
}
@end
@interface Caveats : NSObject
- ( void ) carefulNow;
@end
@implementation Caveats
- ( void ) beware:( float ) whoops {
NSLog(@"%f", whoops);
}
- ( float ) incrementFloat:( float ) digits {
return digits + 1.0;
}
- ( void ) carefulNow {
SEL selector = @selector(beware:);
IMP method = [self methodForSelector: selector];
float test = 123.456;
/* works, obvs */
[self beware: test];
/*
123.456001
*/
/* bork */
objc_msgSend( self, selector, test);
/*
0.000000
*/
/* bork */
method( self, selector, test );
/*
0.000000
*/
/* works */
((void (*) (id, SEL,float))method)(self,selector,test);
/*
123.456001
*/
/* works */
((void (*) (id, SEL, float))objc_msgSend)(self,selector,test);
/*
123.456001
*/
selector = @selector(incrementFloat:);
float result = ((float (*) (id, SEL, float))objc_msgSend)(self,selector,test);
NSLog(@"%f", result);
/*
124.456001
*/
method = [self methodForSelector:selector];
result = ((float (*) (id, SEL, float))method)(self,selector,test);
NSLog(@"%f", result);
/*
124.456001
*/
}
@end
@interface Invoker : NSObject
- (void) test;
@end
@implementation Invoker
-(float) incrementFloat:( float ) farg paramString:( NSString * ) sarg {
NSLog(@"%@, %f", sarg, farg);
return farg + 1.0;
}
- (void) test {
NSInvocation * invocation;
NSMethodSignature * methsig;
SEL selector;
float test_val = 123.456;
float test_result = 0.0;
NSString * test_string = @"Hai float: ";
selector = @selector(incrementFloat:paramString:);
methsig = [self methodSignatureForSelector:selector];
invocation = [NSInvocation invocationWithMethodSignature:methsig];
[invocation setTarget:self];
[invocation setSelector:selector];
[invocation setArgument:&test_val atIndex:2];
[invocation setArgument:&test_string atIndex:3];
[invocation invoke];
[invocation getReturnValue:&test_result];
NSLog(@"%f", test_result);
/*
[2086:403] Hai float: , 123.456001
[2086:403] 124.456001
*/
}
@end
int main( int argc, const char *argv[] ) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
AmazingClass * amazeballs = [[AmazingClass alloc] init];
BoringClass * yawnorama = [[BoringClass alloc] init];
Caveats * caveat = [[Caveats alloc] init];
Invoker * invoker = [[Invoker alloc] init];
[amazeballs doMultipleAmazingStuff];
[yawnorama doBoringStuff];
[caveat carefulNow];
[invoker test];
[amazeballs release];
[yawnorama release];
[caveat release];
[invoker release];
[pool drain];
}
@enigmaticape
Copy link
Author

Single file testable version of the code discussed in Enigmatic Ape Blog post at http://www.enigmaticape.com/blog/objc-invoking-a-selector-with-multiple-parameters/

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