在初始化时,alloc返回的Objective-C类与错误的类混淆

ren*_*ene 5 objective-c xcode4

我认为我理解基本的Objective-C就alloc和init ...方法而言,但显然我没有.我把我遇到的问题归结为下面的最小例子.(对于示例,我将所有源都放在一个文件中,但如果源被分成多个源文件和头文件,问题就会发生,就像通常那样).

以下是代码执行操作以及运行时会发生什么的概要.

我定义了两个类MyInteger和MyFloat,它们几乎完全相同,除了一个使用float处理类型为int的另一个类.两者都有名为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)
Run Code Online (Sandbox Code Playgroud)

使用MyInteger定义,令我惊讶的是:


[4192:903] float parameter value:0.000000
[4192:903] Value:0.000000
(next two output lines omitted)
Run Code Online (Sandbox Code Playgroud)

在我看来,编译器处理对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
Run Code Online (Sandbox Code Playgroud)

请解释发生了什么!我现在已经挣扎了超过24小时,甚至到了打开大门以让一些热量消失的程度,所以我的电脑可能会降温:-)谢谢!

另一个奇怪的是,如果我将MyInteger的定义移到MyFloat的定义之下,那么一切都"好" - 按照我的预期工作.历史证明我经常错误地怀疑编译器应该受到指责.在任何情况下,这里是编译器和项目信息:使用Xcode 4.0.2.尝试使用所有3个编译器选项(GCC 4.2,LLVM GCC 4.2和LLVM编译器2.0).此示例的Xcode项目是使用基于Foundation的Mac OS X命令行工具的标准配置设置的.


#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;
}
Run Code Online (Sandbox Code Playgroud)

She*_*ley 5

+alloc原型为返回id,当编译器面临多种-initWithValue:方法的选择时,它会生成用于调用它找到的第一个方法的代码.当MyInteger被定义时,这意味着编译器将生成转换50.0的代码并将其作为整数参数传递.请注意,整数和浮点参数的传递方式不同,前者在堆栈上,后者在浮点寄存器中.

在运行时,因为动态处理消息调度,所以调用了正确的方法 - 但该方法假定value参数是在浮点寄存器中传递的.但这不是调用代码所在的位置,因此被调用的方法在读取该寄存器时会得到不正确的结果.

类型转换是有效的,因为它明确地告诉编译器在运行时将调用哪些方法,这允许它生成正确的调用代码,传入value浮点寄存器而不是堆栈.

编辑:所有这一切,NSResponder的答案也提出了一些非常好的观点.在Objective-C中,声明共享相同名称但具有不同签名(即参数和返回类型)的方法是一个非常糟糕的主意,并且命名的方法-initWithValue:暗示其参数是NSValue对象.