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

and*_*ewg 7 objective-c

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

if([NSObject respondsToSelector: @selector(init)] == YES )
     [NSObject performSelector: @selector(init)];
Run Code Online (Sandbox Code Playgroud)

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

Mar*_*n R 11

简答:

  • 您可以将任何NSObject 实例方法(例如 respondsToSelector:init)发送到NSObject 或继承自的任何类NSObject.
  • [NSObject init] 在CoreFoundation中重写并抛出运行时异常(对于在OS X 10.6或更高版本上链接的二进制文件).

答案很长:

让我们从你的上一个问题开始:

此外,既然respondsToSelector是实例方法,为什么甚至可以首先调用它?

respondsToSelector:是类符合的NSObject协议的实例方法NSObject.现在,NSObject类(及其每个子类)是一个对象和根类的子类的实例NSObject.

这在Greg Parker的文章[objc explain]中解释和说明 :类和元类 (重点补充):

更重要的是元类的超类.元类的超类链与类的超类链平行,因此类方法与实例方法并行继承.根元类的超类是根类, 因此每个类对象都响应根类的实例方法.最后,类对象是根类的(子类)的实例,就像任何其他对象一样.

这就解释了为什么你可以发送respondsToSelector:NSObject课堂上.返回值是YES指具有给定选择器的类方法.

这是另一个例子:

NSString *path = [NSString performSelector:@selector(pathWithComponents:) withObject:@[@"foo", @"bar"]];
Run Code Online (Sandbox Code Playgroud)

performSelector:withObject:是一个实例方法NSObject,你可以发送该消息的NSString .在这种情况下,结果与之相同

NSString *path = [NSString pathWithComponents:@[@"foo", @"bar"];
Run Code Online (Sandbox Code Playgroud)

现在回答你的初步问题:

为什么respondsToSelector使用选择器"init"在NSObject上运行[NSObject init]会返回1,即使运行会产生运行时错误?

使用与上面相同的推理,必须可以将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;
}
Run Code Online (Sandbox Code Playgroud)

这解释了原因

[NSObject respondsToSelector:@selector(init)] == YES
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

所以这个CoreFoundation方法负责异常.


我不知道为什么init在CoreFoundation中覆盖该方法(而不是直接抛出异常NSObject),并且替换的方法似乎不是开源存储库的一部分http://opensource.apple.com/source/CF/CF -855.14 / (至少我找不到它).

但有一点可能是观察到的行为在OS版本之间发生了变化.如果

 id x = [NSObject init];
Run Code Online (Sandbox Code Playgroud)

在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
Run Code Online (Sandbox Code Playgroud)

_CFExecutableLinkedOnOrAfter()(来自http://www.opensource.apple.com/source/CF/CF-476.14/CFPriv.h)检查二进制文件是否已在OS X 10.6或更高版本上链接,并在这种情况下抛出异常.

因此init,必须在早期的OS版本中允许调用类对象,并且CoreFoundation仍然允许它在"旧二进制文件"中用于向后兼容.