即使运行 [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/

    10-13 06:35