我以为我对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/

10-09 17:53