为什么在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仍然允许它在"旧二进制文件"中用于向后兼容.
| 归档时间: |
|
| 查看次数: |
1403 次 |
| 最近记录: |