开发者

ObjC internals. Why my duck typing attempt failed?

开发者 https://www.devze.com 2022-12-28 06:57 出处:网络
I\'ve tried to use id to create duck typing in objective-c. The concept looks fine in theory but failed in practice. I was unable to use any parameters in my methods. The methods were called but param

I've tried to use id to create duck typing in objective-c. The concept looks fine in theory but failed in practice. I was unable to use any parameters in my methods. The methods were called but parameters were wrong. I was getting BAD_ACESS for objects and random values for primitives. I've attached a simple example below.

The question: Does any one knows why the methods parameters are wrong? What is happening under the hood of the objective-c?

Note: I'm interest in the details. I know how to make the example below work.

An example: I've created a simple class Test that is passed to an other class using property id test.

@implementation Test
- (void) aSampleMethodWithFloat:(float) f andInt: (int) i {
    NSLog(@"Parameters: %f, %i\n", f, i);
}
@end

Then in the class the following loop is executed:

for (int i=0; i < 10; ++i) {
    float f=i*0.1f;
    [tst aSampleMethodWithFloat:f andInt:i]; // warning no method found.
}

Here is the output that I'm getting. As you can see the method was called but the parameters were wrong.

Parameters: 0.000000, 0
Parameters: -0.000000, 1069128089
Parameters: -0.000000, 1070176665
Parameters: 2.000000, 1070805811
Parameters: -0.000000, 1071225241
Parameters: 0.000000, 1071644672
Par开发者_高级运维ameters: 2.000000, 1071854387
Parameters: 36893488147419103232.000000, 1072064102
Parameters: -0.000000, 1072273817
Parameters: -36893488147419103232.000000, 1072483532

Update:

I've found out by accident that when I add a declaration of aSampleMethodWith... to the class with for loop the warning disappears and the method on the Test class is called correctly.

Update 2: As pointed out by JeremyP the direct cause of the problem is that the floats are treated as doubles. But anyone knows why? (following the 5why principle :) ).

According to @eman the call is translated to simple C function call and compiler directive to get the SEL. So the @selector gets confused. But why? The compiler have all necessary type informations in the first method call. Does any one knows a good source of information about the Objective-C internals I've search The Objective-C Programming Language but i didn't find the answer.


By default floating point values are passed as doubles, not floats. The compiler does not know, at the point where [tst aSampleMethodWithFloat:f andInt:i]; occurs that it is only supposed to pass a float, so it promotes f to a double. This means that, in the method, when the compiler does know it is dealing with a float, f is the float formed by the first four bytes of the double passed to the method and i is an int formed from the second four bytes of the double passed.

You can fix this by either

  • changing the first parameter of aSampleMethodWithFloat:andInt: to a double
  • importing the interface declaration of Test into the file where you use it.

NB there is no gain except a small amount of space when using floats in C. You might as well use doubles everywhere.


I think JeremyP is correct about the problem being about doubles vs floats. As for implementation details, message dispatch in Objective-C uses the objc_msgSend(id theReceiver, SEL theSelector, ..) C function (for some deep nitty-gritty, see here). You can simulate the same results of method dispatch like so:

SEL theSelector = @selector(aSampleMethodWithFloat:andInt:);
objc_msgSend(self.test, theSelector, 1.5f, 5);

SEL is just a number that corresponds to a function (that is dynamically determined based on the method signature). objc_msgSend then looks up the actual function pointer (of type IMP) of the method and invokes it. Since objc_msgSend has a variable number of arguments, it will just use as many as you pass in. If you were to do:

objc_msgSend(self.test, theSelector, 1.5f);

It would use 1.5f correctly and have junk for the other variable. Since the method signature typically denotes the number of arguments, this is hard to do under normal usage.


You can make the warning go away by making a category like this:

@interface NSObject (MyTestCategory)
- (void) aSampleMethodWithFloat:(float) f andInt: (int) i;
@end


Without a signature available at the calling point, it isn't known what type the parameters are supposed to have. Undefined methods will be assumed to take ... as parameters, which isn't what yours does. If there is any interface seen by the compiler at this point, where the method in question exists, that definition will be used.


The trouble here is with the dividing line between C and Objective-C. The id type specifies any object, but ints and floats are not objects. The compiler needs to know the C type of all the arguments and the return type of any method you call. Without a declaration, it assumes that a method returns id and takes an arbitrary number of id arguments. But id is incompatible with int and float, so the value doesn't get passed correctly. That's why it works correctly when you provide a declaration — then it knows your int is an int and your float is a float.

0

精彩评论

暂无评论...
验证码 换一张
取 消