为什么[NSObject respondsToSelector:@selector(init)]返回1?

时间:2014-05-16 05:04:19

标签: objective-c

为什么在NSObject上使用选择器“init”运行respondsToSelector返回1,即使运行[NSObject init]会产生运行时错误?我知道init是一个实例方法,因此只能在实例而不是类上运行。为什么这会返回运行时错误?

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

此外,由于respondsToSelector是一个实例方法,为什么甚至可以在第一时间调用它?

3 个答案:

答案 0 :(得分:11)

简答:

  • 您可以发送任何NSObject 实例方法(例如 对respondsToSelector: initNSObject), 或者继承自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会覆盖此方法以提供自己的实现。