Internal ObjC Objects. Why did my attempt at duck printing fail?

I tried to use id

to create duck input in objective-c. The concept looks great in theory but not in practice. I was unable to use any parameters in my methods. Methods were called, but the parameters were incorrect. I was getting BAD_ACESS for objects and random values ​​for primitives. I have attached a simple example below.

Question: Does anyone know why the method parameters are wrong? What's going on under the hood of objective-c?

Note. I am interested in details. I know how to do the example below.

Example:   I created a simple class Test

that is passed to another class using a property id test

.

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

      

Then the following loop is executed in the class:

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

      

Here is the result I get. As you can see, the method was called, but the parameters were incorrect.

Parameters: 0.000000, 0
Parameters: -0.000000, 1069128089
Parameters: -0.000000, 1070176665
Parameters: 2.000000, 1070805811
Parameters: -0.000000, 1071225241
Parameters: 0.000000, 1071644672
Parameters: 2.000000, 1071854387
Parameters: 36893488147419103232.000000, 1072064102
Parameters: -0.000000, 1072273817
Parameters: -36893488147419103232.000000, 1072483532

      

Update:

I accidentally found out that when I add a class declaration aSampleMethodWith...

to a class with a loop for

, the warning disappears and the method in the Test class is called correctly.

Update 2: As Jeremy pointed out, the direct cause of the problem is because the floats are treated as doubles. But does anyone know why? (following the 5th principle :)).

According to @eman, the call translates to a simple C function call and compiler directive to get SEL

. So @selector gets confusing. But why? In the first call to the method, the compiler has all the type information it needs. Does anyone know of a good source of information on Objective-C internals that I was looking for in the Objective-C programming language, but I couldn't find an answer.

+2


a source to share


5 answers


By default, floating point values ​​are passed as doubles, not floats. The compiler doesn't know, at the point where it [tst aSampleMethodWithFloat:f andInt:i];

happens, that it should only skip floats, so it promotes f to double. This means that in a method, when the compiler knows it is dealing with a float, f is a float formed by the first four double bytes passed to the method, and i is an int formed from the second four bytes passed twice.

You can fix this with



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

NB there is no gain, except for a little space when using floats in C. You can also use doublings everywhere.

+2


a source


I think JeremyP is right about the problem with doubles versus floats. In terms of implementation details, posting a message in Objective-C uses a 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 this:

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

      

SEL

is just a number corresponding to a function (dynamically defined based on the method signature). objc_msgSend

then it looks at the actual function pointer (of type IMP) of the method and calls it. Since it objc_msgSend

has a variable number of arguments, it will just use as many as you go through. If you need:



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

      

It would use 1.5f correctly and have garbage for another variable. Since a method signature usually denotes the number of arguments, this is difficult to do in normal use.

+1


a source


You can notify a warning by making a category like this:

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

      

0


a source


Without a signature available at the calling point, it is not known what type the parameters should be. Undefined methods are supposed to take ...

as parameters, which is not the case. If there is currently any interface that the compiler sees where this method exists, that definition will be used.

0


a source


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

0


a source







All Articles