2011-04-23 22 views
5

我以爲我瞭解基本的Objective-C,就alloc和init ...方法而言,但顯然我不知道。我已經將我遇到的問題歸結爲下面的最小示例。 (例如,我將所有源代碼放入一個文件中,但如果將源代碼拆分爲多個源文件和頭文件,問題就會與此相同)。alloc返回的Objective-C類與初始化時錯誤的類混淆

下面是代碼的概要以及當我運行它時會發生什麼。

我定義了兩個類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)。此示例的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; 
} 
+0

我已經將Sherm Pendley的答案標記爲正確的答案,因爲它解釋了爲什麼發生這種情況的機制。來自NSResponder的答案也是一個很好的答案,因爲它教授的簽名命名實踐將有助於避免該問題。謝謝你們! – rene 2011-04-25 11:52:52

回答

5

+alloc原型爲返回id,並且當編譯器面臨多個-initWithValue:方法的選擇時,它會生成調用它找到的第一個的代碼。當定義MyInteger時,這意味着編譯器將生成代碼來轉換50.0並將其作爲整數參數傳遞。請注意,整數和浮點型參數的傳遞方式不同,前者在堆棧中,後者在浮點寄存器中傳遞。

在運行時,由於消息分派是動態處理的,因此調用了正確的方法 - 但該方法假定value參數在浮點寄存器中傳遞。但是這不是調用代碼放置的地方,所以被調用的方法在讀取該寄存器時會得到不正確的結果。

類型轉換的工作原理是因爲它明確告訴編譯器在運行時將調用哪些方法,從而允許其生成正確的調用代碼,並將其傳遞到浮點寄存器而不是堆棧中。

編輯:所有這一切,NSResponder的答案也提供了一些非常好的點。在Objective-C中,聲明共享相同名稱但具有不同簽名的方法(即參數&返回類型)以及名爲-initWithValue:的方法意味着它的參數是一個NSValue對象是一個非常糟糕的主意。

+0

謝謝!這聽起來完全像正在發生的事情。 Apple的+ alloc文檔中提到「新實例的isa實例變量被初始化爲一個描述該類的數據結構」,我認爲編譯器知道哪個類已經實例化,並且能夠爲右側生成代碼一。但是我現在意識到它在編譯時並沒有幫助。 Xcode的自動完成足夠聰明,只能建議正確的方法(使用浮點類型) - 這增加了我的困惑。 – rene 2011-04-23 18:41:27

+0

您正在引用的文檔描述了運行時會發生什麼 - 在編譯時,所有編譯器都知道它正在向一個id發送一個「-initWithValue:」消息,該id可以是任何類。 – 2011-04-23 18:43:38

4

這是一個壞主意,有兩個方法簽名的方法名稱是相同的,但類型不相同,尤其是如果它們正在編譯在同一個文件中。編譯器對於消息表達式應該傳遞一個int還是一個float會感到困惑。

如果你看看NSNumber,你會發現有單獨的-initWithFloat:,-initWithDouble:-initWithInt:等方法。另外,如果你的名字中有一個單詞「value」的方法,大多數Cocoa開發人員會認爲它需要參數NSValue

+1

謝謝。我編輯了這個問題,以澄清當源被拆分成不同的.m和.h文件時問題仍然存在。當然,MyInteger.h和MyFloat.h都會包含在進行調用的文件中,所以編譯器在您指出時仍然可能會感到困惑。我會採納您的建議並更改簽名以反映價值類型。不過,我覺得很麻煩,我無法預測行爲,並且行爲會根據MyInteger.h和MyFloat.h導入的順序而變化。那麼,它真的應該如何? – rene 2011-04-23 17:52:48

+0

但是他們在不同的班級,它是否應該知道您將信息發送給哪個班級? – 2015-08-26 08:47:14