我尝试使用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编程语言,但我找不到答案。
答案 0 :(得分:2)
默认情况下,浮点值作为双精度值传递,而不是浮点值。编译器不知道,[tst aSampleMethodWithFloat:f andInt:i];
发生时它只应该传递一个浮点数,所以它将f提升为double。这意味着,在该方法中,当编译器 知道它正在处理float时,f是由传递给方法的double的前四个字节形成的float,并且我是一个int形成的从双尾的后四个字节开始。
您可以通过
解决此问题注意在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是一个浮点数。