2014-05-16 66 views
7

爲什麼在選擇器「init」的NSObject上運行respondsToSelector會返回1,即使運行[NSObject init]給出運行時錯誤?我知道init是一個實例方法,因此只能在實例上運行,而不能在類上運行。爲什麼這會返回運行時錯誤?爲什麼[NSObject respondsToSelector:@selector(init)]返回1?

if([NSObject respondsToSelector: @selector(init)] == YES) 
    [NSObject performSelector: @selector(init)]; 

此外,由於respondsToSelector是一個實例方法,爲什麼它甚至可以調用它擺在首位?

回答

11

答案很簡單:

  • 您可以發送任何NSObject實例方法(如 respondsToSelector:init),以的NSObject類, 或從NSObject繼承任何類。
  • [NSObject init]在CoreFoundation中被重寫並引發運行時異常(對於在OS X 10.6或更高版本上鍊接的 二進制文件)。

龍答:

讓我們先從你的最後一個問題:

此外,由於respondsToSelector是一個實例方法,爲什麼它甚至可以調用它擺在首位?

respondsToSelector:NSObject協議的一個實例方法中,向其中 所述NSObject類符合。現在NSObject類(及其每個子類) 是根類NSObject的子類的對象和實例。

這是解釋和格雷格·帕克的文章 [objc explain]: Classes and metaclasses (強調)所示:

更重要的是元類的超類。元類的超類鏈與類的超類鏈平行,所以類方法與實例方法並行繼承。 而根元類的超類是根類 因此每個類對象都響應根類的實例方法。 最後,類對象是根類(的一個子類)的實例,就像任何其他對象一樣。

這解釋了爲什麼您可以將respondsToSelector:發送到NSObject類。 如果存在給定選擇器的類方法,則返回值爲YES

下面是另一個例子:

NSString *path = [NSString performSelector:@selector(pathWithComponents:) withObject:@[@"foo", @"bar"]]; 

performSelector:withObject:實例方法NSObject,您可以發送該 消息NSString類。在這種情況下,結果是一樣的

NSString *path = [NSString pathWithComponents:@[@"foo", @"bar"]; 

現在您最初的問題:

爲什麼在NSObject的以「初始化」時,選擇運行respondsToSelector返回1 即使運行[NSObject init]給出了運行時錯誤?

隨着與上述相同的理由,它必須是可以發送消息init到從NSObject派生的任何 類。

現在NSObject類方法init,這是記錄在運行時拋出 例外, 看到http://opensource.apple.com/source/objc4/objc4-532.2/runtime/NSObject.mm

// Replaced by CF (throws an NSException) 
+ (id)init { 
    return (id)self; 
} 

這就解釋了爲什麼

[NSObject respondsToSelector:@selector(init)] == YES 

在NSObject的註釋.mm指出+init在CoreFoundation中被覆蓋,事實上, 當拋出異常時,棧回溯是

(lldb) bt 
* thread #1: tid = 0x6eda, 0x01f69952 libsystem_kernel.dylib`__pthread_kill + 10, queue = 'com.apple.main-thread', stop reason = signal SIGABRT 
    .... 
    frame #8: 0x0156b9fc libobjc.A.dylib`objc_exception_throw + 323 
    frame #9: 0x01889931 CoreFoundation`+[NSObject(NSObject) init] + 241 
    * frame #10: 0x00002a51 foo`main(argc=1, argv=0xbfffee30) + 257 at main.m:25 

所以這種方法的CoreFoundation負責例外。


我不知道爲什麼init方法中的CoreFoundation (而不是直接在NSObject拋出異常)覆蓋,同時將被替換 方法似乎不能成爲開源庫的一部分http://opensource.apple.com/source/CF/CF-855.14/ (至少我在那裏找不到)。

但有一個有趣的觀點可能是觀察到的行爲在操作系統版本之間發生了變化。 如果

id x = [NSObject init]; 

在OS X 10.5編譯,然後它的工作原理,並返回一個NSObject類對象。 即使二進制文件已在OS X 10.5上編譯和鏈接,並在OS X 10.9等更高版本的 上運行,也可以使用。

這也可以從

CoreFoundation`+[NSObject(NSObject) init]: 
0x1019d8ad0: pushq %rbp 
0x1019d8ad1: movq %rsp, %rbp 
0x1019d8ad4: pushq %rbx 
0x1019d8ad5: pushq %rax 
0x1019d8ad6: movq %rdi, %rbx 
0x1019d8ad9: movl $0x6, %edi 
0x1019d8ade: callq 0x1018dfcc0    ; _CFExecutableLinkedOnOrAfter 
0x1019d8ae3: testb %al, %al 
0x1019d8ae5: jne 0x1019d8af1    ; +[NSObject(NSObject) init] + 33 
0x1019d8ae7: movq %rbx, %rax 
0x1019d8aea: addq $0x8, %rsp 
0x1019d8aee: popq %rbx 
0x1019d8aef: popq %rbp 
0x1019d8af0: ret  
0x1019d8af1: movq %rbx, %rdi 
0x1019d8af4: callq 0x101a19cee    ; symbol stub for: class_getName 
0x1019d8af9: leaq 0x9fe80(%rip), %rcx  ; kCFAllocatorSystemDefault 
0x1019d8b00: movq (%rcx), %rdi 
0x1019d8b03: leaq 0xc5a66(%rip), %rdx  ; @"*** +[%s<%p> init]: cannot init a class object." 
... 
0x1019d8b72: callq *0x9e658(%rip)   ; (void *)0x00000001016b9fc0: objc_msgSend 
0x1019d8b78: movq %rax, %rdi 
0x1019d8b7b: callq 0x101a19d4e    ; symbol stub for: objc_exception_throw 

_CFExecutableLinkedOnOrAfter()彙編代碼看出(從http://www.opensource.apple.com/source/CF/CF-476.14/CFPriv.h) 檢查如果二進制已在OS X 10.6或更高的聯繫,並拋出在這種情況下,一個異常 。

因此在類對象上調用init必須在早期的OS版本中允許, 和CoreFoundation仍然允許在「舊二進制文件」中向後兼容。

+0

非常好的答案! –

1

respondsToSelector:也是NSProxy的類方法,並且該方法確實將類作爲接收方。從NSProxy文檔,

respondsToSelector: 
Returns a Boolean value that indicates whether the receiving class responds to a given selector. 

+ (BOOL)respondsToSelector:(SEL)aSelector 
Parameters 
aSelector 
A selector. 
Return Value 
YES if the receiving class responds to aSelector messages, otherwise NO. 
1

NSObject的的respondsToSelector在內部實現爲:如果NSObject的實例所提到的選擇作出迴應

+ (BOOL)respondsToSelector:(SEL)aSelector { 
    return class_respondsToSelector([self class], aSelector); 
} 

這只是表明。 各種類重寫此方法以擁有自己的實現。例如,NSProxy重寫此方法以提供自己的實現。

+0

但是什麼是調用該類的方法?看一下NSObject頭顯示「 - (BOOL)respondsToSelector:(SEL)aSelector;」這是實例方法 –

+0

NSObject中沒有記錄類方法。 –

+0

如果沒有respondsToSelector類方法,那麼當您嘗試在類上調用它時會引發異常。由於有一個同名的未公開的類方法,所以它成功執行 –