5

Hi im new to objective C and was wondering if someone might be able to help me with this. I have a few different methods each requiring 3 input values and normally call it using

[self methodA:1 height:10 speed:3]

but the method name I want to read from a string in a plist so for example if the string was methodB i would get

[self methodB:1 height:10 speed:3] 

for "methodC"

[self methodC:1 height:10 speed:3]

and so on.

Any ideas how I might do this I tried defining the string as a Selector using NSSelectorFromString

NSString *string = [plistA objectForKey:@"method"];
SEL select = NSSelectorFromString(string);
[self performSelector:select:c height:b speed:a]; 

However this did not work either any help would be greatly appreciated. Have tried the solution below but could not get to work here is what i've tried.

So just to recap I have methods such as

 spawnEnemyA:2 withHeight:3 withSpeed:4  
 spawnEnemyB:3 withHeight:2 withSpeed:5 

and I want to read the values I want to pass to these methods as well as the method type from a plist file. my code is as follows, //////////////////////////////////////////////////////////////

//These are the values I read from the plist that I want my method to use

    int a = [[enemySettings objectForKey:@"speed"] intValue];
    int b = [[enemySettings objectForKey:@"position"] intValue];
    int c = [[enemySettings objectForKey:@"delay"] intValue];

   // I Also read the method name from the plist and combine it into a single string  
    NSString *method = [enemySettings objectForKey:@"enemytype"];
    NSString *label1 = @"spawn";
    NSString *label2 = @":withHeight:withSpeed:";
    NSString *combined = [NSString stringWithFormat:@"%@%@%@",label1, method,label2];


    //Check that the string is correct get spawnEnemyA:withHeight:withSpeed:
    CCLOG(@"%@",combined);


//This is the Invocation part 
    NSInvocation * invocation = [ NSInvocation new ];

    [ invocation setSelector: NSSelectorFromString(combined)];
    [ invocation setArgument: &c atIndex: 2 ];
    [ invocation setArgument: &b atIndex: 3 ];
    [ invocation setArgument: &a atIndex: 4 ];

    [ invocation invokeWithTarget:self ];

    [invocation release ];

////////////////////////////////////////////////////////////////////

The code compiles without any errors but the methods are not called. Any ideas? Cheers

4 Answers 4

12

You can't use performSelector for a method with 3 (or more) arguments.
But for your information, here's how to use it:

SEL m1;
SEL m2;
SEL m3;

m1 = NSSelectorFromString( @"someMethodWithoutArg" );
m2 = NSSelectorFromString( @"someMethodWithAnArg:" );
m1 = NSSelectorFromString( @"someMethodWithAnArg:andAnotherOne:" );

[ someObject performSelector: m1 ];
[ someObject performSelector: m2 withObject: anArg ];
[ someObject performSelector: m2 withObject: anArg withObject: anOtherArg ];

For methods with more than 2 arguments, you will have to use the NSInvocation class.
Take a look at the documentation to learn how to use it.

Basically:

NSInvocation * invocation = [ NSInvocation new ];

[ invocation setSelector: NSStringFromSelector( @"methodWithArg1:arg2:arg3:" ) ];

// Argument 1 is at index 2, as there is self and _cmd before
[ invocation setArgument: &arg1 atIndex: 2 ];
[ invocation setArgument: &arg2 atIndex: 3 ];
[ invocation setArgument: &arg3 atIndex: 4 ];

[ invocation invokeWithTarget: targetObject ];

// If you need to get the return value
[ invocation getReturnValue: &someVar ];

[ invocation release ];
Sign up to request clarification or add additional context in comments.

10 Comments

A fan of spaces, I see. :) This'll work, too. For calls like these, I prefer the casted objc_msgSend() for both speed and, most importantly, type safety in that the compiler will complain if I screw up the types later.
I totally agree about speed. But for a beginner, I think the Objective-C way may be a bit less confusing, even if it's pretty important to know how the runtime works. I don't agree about type safety, as IMHO, both ways are type unsafe. That's the purpose of objc_msgSend. Won't be generic otherwise. Anyway, thanks for your comment. : ) (and btw, yes, I love spaces!)
Actually, the cast objc_msgSend() is considerably more type safe assuming you get the cast correct. The arguments to the invocation are, effectively, untyped. And that code is wrong; you would need to pass the address of the arguments (which I didn't catch the first read)! All that pointer magic goop certainly adds a lot of complexity.
Corrected the setArgument issue, thanks to point it out. : ) About type safety, you say: «assuming the cast is correct». This is not really type safety, as you can just cast to any type. That kind of cast just tells the compiler to shut-up. About arguments, as the objc_msgSend prototype has ..., you can also pass everything (also because objc_msgSend is actually implemented using assembly, and then doesn't care much about types). You add some safety in your example by casting the function pointer. This is clever, but I don't think it's safer than NSInvocation...
And I don't know if it's really the place for that kind of discussion, even if I would surely envoy such an argumentation : D
|
4

In general, this kind of dynamism often indicates an anti-pattern. Colluding data with implementation in this fashion is not generally a best practice.

Sometimes, though, it is necessary. If you are going down this path, then given that your various method declarations likely look like:

- (void)methodAWidth:(NSUInteger)w height:(NSUInteger)h speed:(NSUInteger)s;
- (void)methodBWidth:(NSUInteger)w height:(NSUInteger)h speed:(NSUInteger)s;
- (void)methodCWidth:(NSUInteger)w height:(NSUInteger)h speed:(NSUInteger)s;

You would probably want something like:

NSString *selName = [NSString stringWithFormat:@"method%@Width:height:speed:", ... one of @"A", @"B", or @"C" ....];
SEL selector = NSelectorFromString(selName);

Then:

if (![target respondsToSelector:selector])
    return; // no can do

void (*castMsgSend)(id, SEL, NSUInteger, NSUInteger, NSUInteger) = (void*)objc_msgSend;
castMsgSend(target, selector, 1, 10, 3);

Every method call is compiled down to a call to objc_msgSend(). By doing the above, you are creating a fully type-safe/type-checked call site that goes through the normal Objective-C messaging mechanism, but the selector is dynamically defined on the fly.\

While performSelector: (and multi-arg variants) are handy, they can't deal with non-object types.


And, as MacMade pointed out in a comment, watch out for floating point and structure returns. They use different variants of objc_msgSend() that the compiler automatically handles in the normal [foo bar] case.

1 Comment

When i try this I get the following error message for the void (castMSgSend) part Cannot initialize a variable of type 'void()(id, SEL, NSUInteger, NSUInteger,NSUInteger)' with an rvalue of type 'void *' How can I fix this? Cheers @bbum
1

You can directly use objc_msgsend:

NSString *methodName = [plistA objectForKey:@"method"];
objc_msgSend(self, methodName, c, b, a);

Mind that the selector must include all pieces, eg @"method:height:speed:"

3 Comments

You need to cast the call to objc_msgSend() for this to be correct.
Maybe a bit low-level for a beginner ; ) Also keep in mind that, if you need a return value, there is also objc_msgSend_fpret (floating point return) and objc_msgSend_stret (struct return).
Yes, I can imagine that it can be somewhat lowlevel, but at least you can learn how the Obj-C is built on C :) Take a look here for more details on this method: stackoverflow.com/questions/2573805/…
-1

You should replace the following line with:

[self performSelector:select:c height:b speed:a];

and write following:

[self performSelector:select withObject:[NSArray arrayWithObjects:c,b,a,nil]];

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.