5

How can I run an arbitrary selector on an object, the return of which is a double? For example, I have obj A, which has method -(double)blah;

How can I do double res = [obj performSelector:@selector(blah)];? performSelector returns an id type object, so should I cast from id to NSInteger to double - that will lose precision?

Also, I do not want to use the obj's methodSignatureForSelector (meaning, no NSMethodSignature and no NSInvocation) because it is a huge CPU drain at run-time.

AWF4vk
  • 5,810
  • 3
  • 37
  • 70

3 Answers3

8

You may want to take a look at the Objective-C runtime functions, especially objc_msgSend_fpret.

double objc_msgSend_fpret( id self, SEL op, ... )

Which sends a message with a floating-point return value to an instance of a class.

The performSelector methods use objc_msgSend, which returns an id type.

For instance:

double res = objc_msgSend_fpret( obj, @selector( blah ) );

You'll need to import this objc runtime header:

#import <objc/message.h>

EDIT

By the way, here's the link to the ObjC runtime reference: http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ObjCRuntimeRef/Reference/reference.html

EDIT 2 - IMPORTANT

objc_msgSend_fpret is implemented in different ways, depending on the CPU architecture (basically, i386 or x86_64).

As I said in a comment, those functions are implemented using assembly, so their implementations depends on the CPU architecture.

Under the x86_64 architecture, this function returns a long double.

This is why it fails (and returning NAN) when you assign it to a double.

Also note that there is an objc_msgSend_fp2ret function.

So, basically, my previous example will not work:

double x = objc_msgSend_fpret( obj, @selector( blah ) );
printf( "Val: %f\n", x );

As you noticed, it will print 'NAN'.

To make it work, you'll have to do it this way:

long double x = objc_msgSend_fpret( obj, @selector( blah ) );
printf( "Val: %Lf\n", x );

Here's a working example:

http://www.eosgarden.com/uploads/misc/fp.m

Compile it using:

gcc -Wall -framework Foundation -o fp fp.m
Macmade
  • 52,708
  • 13
  • 106
  • 123
  • `objc_msgSend` and friends are declared in `objc/message.h`. On Mac OS X, you can just import `objc/objc-runtime.h`, but on iOS you must import `objc/message.h`. I edited the answer. – rob mayoff Nov 11 '11 at 00:38
  • @Macmade Weird thing, but although it should work, it returns NAN, where the IMP method returns the valid result. Not sure if I'm missing something. – AWF4vk Nov 11 '11 at 00:45
  • @Macmade Chuck would win, but I don't like that it creates an NSNumber object, just to be cast to doubleValue. Plus, msgSend still doesn't work... ` long double x = objc_msgSend_fpret( [NSDate date], @selector( timeIntervalSinceReferenceDate ) );` x = NAN. – AWF4vk Nov 11 '11 at 01:09
  • There's no prefect way. It all depends on what you want. Often, when dealing with such problems, performance may lead to a different approach (which can also lead to problems). It's also different if you actually wrote your `blah` method, or if it comes from a library/framework. Hope you'll find the right way : ) – Macmade Nov 11 '11 at 01:13
7

If it's a method with no arguments, you can use valueForKey: and doubleValue on the value returned from that method. Otherwise, I think you'll have to muck with objc_msgSend_fpret to make it work.

Chuck
  • 234,037
  • 30
  • 302
  • 389
  • The value that's returned from that method **is** a double, so using 'doubleValue' on it or 'valueForKey:' doesn't make sense. – AWF4vk Nov 11 '11 at 00:41
  • @David: valueForKey: boxes primitives, so yes, what I said does make sense. – Chuck Nov 11 '11 at 01:00
  • I stand corrected. I like your solution. It'd be the best, except it creates a temp NSNumber object. – AWF4vk Nov 11 '11 at 01:10
4

Uses least memory (valueForKey: uses two temporary objects) and the msgSend method doesn't work.

 IMP myImp1 = [obj methodForSelector:@selector(getDouble)];
 double aDouble1 = ((double (*) (id,SEL))myImp1)(obj,@selector(getDouble));
AWF4vk
  • 5,810
  • 3
  • 37
  • 70
  • Maybe you would explain why you think this is a better solution? – Macmade Nov 11 '11 at 00:36
  • No problem : ) In that specific case, I think using `objc_msgSend_fpret` is better, and actually more readable. You should always use function's casts with caution. – Macmade Nov 11 '11 at 00:42
  • Definitely more readable. Safety-wise, not sure what happens internally, but I can't imagine it does much more than expand to the IMP solution. – AWF4vk Nov 11 '11 at 00:44
  • Actually, all the objc_msgSend* functions are implemented using assembly, so it might do a lot of things differently. – Macmade Nov 11 '11 at 00:45
  • @Macmade I tried your method in favor of readability, but it doesn't work. It only returns NAN, whereas the IMP method returns the valid results. Take for example ` NSTimeInterval t = objc_msgSend_fpret([NSDate date], @selector(timeIntervalSinceReferenceDate));` Returns NAN. – AWF4vk Nov 11 '11 at 00:50
  • Ok, see it now... Check the edit on my answer in a few minutes – Macmade Nov 11 '11 at 00:56