即使运行 [NSObject init] 会出现运行时错误,为什么在 NSObject 上使用选择器“init”运行 RespondsToSelector 会返回 1?我知道 init 是一个实例方法,因此只在实例上运行而不是在类上运行。为什么这会返回运行时错误?
if([NSObject respondsToSelector: @selector(init)] == YES )
[NSObject performSelector: @selector(init)];
此外,既然 RespondsToSelector 是一个实例方法,为什么一开始就可以调用它呢?
最佳答案
简答:
NSObject
实例方法(例如respondsToSelector:
或 init
) 到 NSObject
类,或任何继承自
NSObject
的类。 [NSObject init]
在 CoreFoundation 中被覆盖并抛出运行时异常(对于在 OS X 10.6 或更高版本上链接的二进制文件)。
长答案:
让我们从最后一个问题开始:
respondsToSelector:
是 NSObject
协议(protocol)的一个实例方法,NSObject
类符合。现在 NSObject
类(及其每个子类)是一个对象,也是根类
NSObject
的子类的一个实例。这在 Greg Parker 的文章中有解释和说明
[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"];
现在回答你最初的问题:
同上推理,必须可以将
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 方法负责异常。
我不知道为什么在 CoreFoundation 中覆盖了
init
方法(而不是直接在
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
,并且 CoreFoundation 仍然允许在“旧二进制文件”中使用它以实现向后兼容性。
关于objective-c - 为什么 [NSObject RespondsToSelector :@selector(init)] return 1?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/23692980/