为什么在NSObject上使用选择器“init”运行respondsToSelector返回1,即使运行[NSObject init]会产生运行时错误?我知道init是一个实例方法,因此只能在实例而不是类上运行。为什么这会返回运行时错误?
if([NSObject respondsToSelector: @selector(init)] == YES )
[NSObject performSelector: @selector(init)];
此外,由于respondsToSelector是一个实例方法,为什么甚至可以在第一时间调用它?
答案 0 :(得分:11)
简答:
NSObject
实例方法(例如
对respondsToSelector:
类的init
或NSObject
),
或者继承自NSObject
。[NSObject init]
在CoreFoundation中被重写并抛出运行时异常(for
在OS X 10.6或更高版本上链接的二进制文件。长答案:
让我们从你的上一个问题开始:
此外,由于
respondsToSelector
是一个实例方法,为什么甚至可以在第一时间调用它?
respondsToSelector:
是NSObject
协议的实例方法
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"];
现在回答您的初步问题:
为什么在NSObject上使用选择器" init"运行
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中的注释表明在CoreFoundation中覆盖了+init
,事实上,
抛出异常时,堆栈回溯是
(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/的一部分
(至少我在那里找不到它。)
但有一点可能是观察到的行为在OS版本之间发生了变化。 如果
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或更高版本上链接,并引发异常
在这种情况下。
因此,在早期的OS版本中必须允许在类对象上调用init
,
和CoreFoundation仍然允许它在旧的二进制文件&#34;为了向后兼容。
答案 1 :(得分: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.
答案 2 :(得分:1)
NSObject的respondsToSelector
内部实现为:
+ (BOOL)respondsToSelector:(SEL)aSelector {
return class_respondsToSelector([self class], aSelector);
}
这仅表示NSObject的实例是否响应上述选择器。 各种类重写此方法以拥有自己的实现。例如,NSProxy会覆盖此方法以提供自己的实现。