ObjC内部。为什么我的鸭子打字尝试失败?

时间:2010-04-25 18:31:05

标签: objective-c

我尝试使用id在objective-c中创建duck typing。这个概念在理论上看起来不错,但在实践中失败了我无法在我的方法中使用任何参数。这些方法被调用但参数错误。对于基元的对象和随机值,我得到了BAD_ACESS。我在下面附上了一个简单的例子。

问题: 有没有人知道为什么方法参数是错误的? 在Objective-c的引擎下发生了什么?

注意:我对细节很感兴趣。我知道如何使下面的例子工作。

示例:     我创建了一个简单的类Test,它使用属性id test传递给另一个类。

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

然后在类中执行以下循环:

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

这是我得到的输出。正如您所看到的那样,方法被调用但参数错误。

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

更新

我偶然发现当我使用aSampleMethodWith...循环向类添加for声明时,警告消失,并且正确调用Test类上的方法。

更新2: 正如JeremyP所指出的那样,问题的直接原因是浮子被视为双打。但谁知道为什么? (遵循5why原则:))。

根据@eman,调用被转换为简单的C函数调用和编译器指令以获取SEL。所以@selector会感到困惑。但为什么?编译器在第一个方法调用中具有所有必需的类型信息。有没有人知道关于Objective-C内部的一个很好的信息来源我已经搜索了Objective-C编程语言,但我找不到答案。

5 个答案:

答案 0 :(得分:2)

默认情况下,浮点值作为双精度值传递,而不是浮点值。编译器不知道,[tst aSampleMethodWithFloat:f andInt:i];发生时它只应该传递一个浮点数,所以它将f提升为double。这意味着,在该方法中,当编译器 知道它正在处理float时,f是由传递给方法的double的前四个字节形成的float,并且我是一个int形成的从双尾的后四个字节开始。

您可以通过

解决此问题
  • 将aSampleMethodWithFloat:andInt:的第一个参数更改为double
  • 将Test的接口声明导入您使用它的文件中。

注意在C中使用花车时除了少量空间外没有任何好处。你可以在任何地方使用双打。

答案 1 :(得分:1)

我认为JeremyP关于双打与浮动的问题是正确的。至于实现细节,Objective-C中的消息调度使用objc_msgSend(id theReceiver, SEL theSelector, ..) C函数(对于一些深层次的细节,请参阅here)。你可以像这样模拟方法调度的相同结果:

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

SEL只是一个与函数对应的数字(根据方法签名动态确定)。然后objc_msgSend查找方法的实际函数指针(IMP类型)并调用它。由于objc_msgSend具有可变数量的参数,因此它将使用您传入的数量。如果您要这样做:

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

它会正确使用1.5f并且对另一个变量有垃圾。由于方法签名通常表示参数的数量,因此在正常使用情况下这很难做到。

答案 2 :(得分:0)

你可以通过制作这样的类别来消除警告:

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

答案 3 :(得分:0)

如果在调用点没有签名,则不知道参数应该具有什么类型。假设未定义的方法将...作为参数,这不是你的工作。如果此时编译器看到任何接口,存在相关方法,则将使用该定义。

答案 4 :(得分:0)

这里的麻烦在于C和Objective-C之间的分界线。 id类型指定任何对象,但是int和float不是对象。编译器需要知道所有参数的C类型以及您调用的任何方法的返回类型。没有声明,它假定一个方法返回id并获取任意数量的id参数。但是id与int和float不兼容,因此值无法正确传递。这就是为什么它在你提供声明时正常工作 - 然后它知道你的int是一个int而你的float是一个浮点数。