Pedro Vieira
Pedro Vieira

Reputation: 3370

How to call an object's method from a selector (from a string) and get the returning value of that method (no matter what type is)?

Ok, here's what I have:

SEL propertySelector = NSSelectorFromString(keyPath);
if ([obj respondsToSelector:propertySelector]){
    id propertyValue = [obj performSelector:propertySelector];
}

So, the keyPath is a NSString and it's used to pass the string name of a method. After that, I create a @selector from that string using NSSelectorFromString() and I check if the object (obj) responds to that selector.
But now here's the problem, I want to get the returning value of the method that I want to call, but it seems like the code above doesn't work with type values like int, NSInteger, etc.. because I'm using id for the propertyValue (if I call the method above in the middle of another method, and the keyPath is referencing a method that returns an int, the first method will stop being executed).

How can I receive the value of the method no matter what type it is?
Thanks!

Upvotes: 0

Views: 935

Answers (3)

newacct
newacct

Reputation: 122439

If you know what you're doing, you can use objc_msgSend directly:

NSInteger (*f)(id, SEL) = (NSInteger (*)(id, SEL))objc_msgSend;
SEL propertySelector = NSSelectorFromString(keyPath);
if ([obj respondsToSelector:propertySelector]){
    NSInteger retVal = f(obj, propertySelector);
}

Upvotes: 1

jscs
jscs

Reputation: 64002

If you're accessing a property, you should definitely consider KVC, which boxes up the primitive into an NSNumber for you.

NSInteger val = [[obj valueForKeyPath:keyPath] integerValue];

As for your literal question, the performSelector: docs tell you what to do.

For methods that return anything other than an object, use NSInvocation.

Which looks like this:

NSInteger retVal;
SEL sel = NSSelectorFromString(keyPath);
NSMethodSignature * sig = [obj methodSignatureForSelector:sel];
NSInvocation * inv = [NSInvocation invocationWithMethodSignature:sig];
[inv setTarget:obj];
[inv setSelector:sel];
[inv invoke];
[inv getReturnValue:&retVal];

// Use retVal

Upvotes: 5

Guillaume Algis
Guillaume Algis

Reputation: 11016

The following works like a charm for me:

- (void)foo {
    SEL propertySelector = NSSelectorFromString(@"doSomething");
    if ([self respondsToSelector:propertySelector]){
        id propertyValue = [self performSelector:propertySelector];
        NSLog(@"%@", propertyValue);
    }
}

- (NSNumber *)doSomething {
    return @42;
}

Output is, as expected:

42

There could eventually be a problem if you method return type is not an object (and this seems to be the case based on your edit). From the doc:

performSelector:

Sends a specified message to the receiver and returns the result of the message. (required)

Return Value

An object that is the result of the message.

(emphasis on object).

If you try to use performSelector: on a selector which return type is not a object, you will get an EXC_BAD_ACCESS.

Which means you must change your return type and encapsulate the returned value in an object (eg. NSNumber or NSValue). Note that in the case of an int or NSInteger or other "number" types, this can be done by enclosing the value with @() (for example return 3.2; becomes return @(3.2);).

Upvotes: 0

Related Questions