Created
November 9, 2012 14:26
-
-
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 ?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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]; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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/