我以为我对alloc和init ...方法了解得很基本,但是我显然不了解。我将我遇到的问题归结为下面的最小示例。 (对于该示例,我将所有源文件放入一个文件中,但是如果将源文件按正常方式拆分为多个源文件和头文件,则问题会发生相同的情况)。
这是代码的作用以及运行代码时发生的情况的提要。
我定义了两个类MyInteger和MyFloat,它们几乎相同,除了一个类处理int类型,另一个处理float类型。两者都有称为initWithValue:的初始化方法,但是参数类型不同。有一个#define来控制是否定义MyInteger类,原因是,即使从未使用过该类,它也会导致程序的不同行为。
main()仅使用MyFloat。前两行执行此操作:分配MyFloat的实例,并将其初始化为50.0的值。然后打印该值。根据是否定义了MyInteger,我得到两个不同的输出。
正如我所期望的那样,没有定义MyInteger:
[4138:903] float parameter value:50.000000
[4138:903] Value:50.000000
(next two output lines omitted)
定义了MyInteger之后,令我惊讶的是:
[4192:903] float parameter value:0.000000
[4192:903] Value:0.000000
(next two output lines omitted)
在我看来,编译器将对initWithValue:的调用视为属于MyInteger类。 main()的后两行通过将[MyFloat alloc]转换为MyFloat *类型来对其进行测试。即使定义了MyInteger,它也确实会产生预期的输出:
[4296:903] float parameter value:0.000000
[4296:903] Value:0.000000
[4296:903] float parameter value:50.000000
[4296:903] Value with cast:50.000000
请说明发生了什么!我已经为它苦苦挣扎了24多个小时,甚至还没有打开门让热量散开,以至于我的计算机可能会冷却下来:-)谢谢!
另一个奇怪的是,如果我将MyInteger的定义向下移动到MyFloat的定义之下,那么一切都是“好”的-正如我期望的那样工作。历史证明我经常犯错,以至于我怀疑编译器应该受到谴责。无论如何,以下是编译器和项目信息:使用Xcode 4.0.2。尝试使用所有3个编译器选项(GCC 4.2,LLVM GCC 4.2和LLVM Compiler 2.0)。使用基于Foundation的Mac OS X命令行工具的标准配置来设置此示例的Xcode项目。
#define DO_DEFINE_MYINTEGER 1
//------------- define MyInteger --------------
#if DO_DEFINE_MYINTEGER
@interface MyInteger : NSObject {
int _value;
}
-(id)initWithValue:(int)value;
@end
@implementation MyInteger
-(id)initWithValue:(int)value {
self= [super init];
if (self) {
_value= value;
}
return self;
}
@end
#endif
//------------- define MyFloat --------------
@interface MyFloat : NSObject {
float _value;
}
-(id)initWithValue:(float)value;
-(float)theValue;
@end
@implementation MyFloat
-(id)initWithValue:(float)value {
self= [super init];
if (self) {
NSLog(@"float parameter value:%f",value);
_value= value;
}
return self;
}
-(float)theValue {
return _value;
}
@end
//--------------- main ------------------------
int main (int argc, const char * argv[])
{
MyFloat *mf1= [[MyFloat alloc] initWithValue:50.0f];
NSLog(@"Value:%f",[mf1 theValue]);
MyFloat *mf2= [((MyFloat*)[MyFloat alloc]) initWithValue:50.0f];
NSLog(@"Value with cast:%f",[mf2 theValue]);
return 0;
}
最佳答案
+alloc
的原型(prototype)设计为返回id
,当编译器面对多种-initWithValue:
方法的选择时,它将生成用于调用找到的第一个方法的代码。定义MyInteger时,这意味着编译器将生成代码以转换50.0并将其作为整数参数传递。请注意,整数和浮点参数的传递方式不同,前者在堆栈上,后者在浮点寄存器中。
在运行时,由于消息分发是动态处理的,因此将调用正确的方法-但该方法假定value
参数已在浮点寄存器中传递。但这不是调用代码所放置的位置,因此被调用的方法在读取该寄存器时会得到错误的结果。
类型转换之所以起作用,是因为它明确地告诉编译器在运行时将调用哪些方法,从而允许它生成正确的调用代码,并在浮点寄存器中而不是在堆栈上传递value
。
编辑:综上所述,NSResponder的回答也提出了一些很好的观点。在Objective-C中,声明共享相同名称但具有不同签名(即参数和返回类型)的方法是一个非常糟糕的主意,而名为-initWithValue:
的方法意味着其参数是一个NSValue对象。
关于分配返回的Objective-C类在初始化时与错误的类混淆,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/5765743/