龍答:
讓我們先從你的最後一個問題:
此外,由於respondsToSelector
是一個實例方法,爲什麼它甚至可以調用它擺在首位?
respondsToSelector:
是NSObject
協議的一個實例方法中,向其中 所述NSObject
類符合。現在NSObject
類(及其每個子類) 是根類NSObject
的子類的對象和實例。
這是解釋和格雷格·帕克的文章 [objc explain]: Classes and metaclasses (強調)所示:
更重要的是元類的超類。元類的超類鏈與類的超類鏈平行,所以類方法與實例方法並行繼承。 而根元類的超類是根類 因此每個類對象都響應根類的實例方法。 最後,類對象是根類(的一個子類)的實例,就像任何其他對象一樣。
這解釋了爲什麼您可以將respondsToSelector:
發送到NSObject
類。 如果存在給定選擇器的類方法,則返回值爲YES
。
下面是另一個例子:
NSString *path = [NSString performSelector:@selector(pathWithComponents:) withObject:@[@"foo", @"bar"]];
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仍然允許在「舊二進制文件」中向後兼容。
非常好的答案! –