Why does running respondsToSelector on NSObject with the selector "init" return 1 even though running [NSObject init] gives a runtime error? I know that init is an instance method and therefore only runs on instances and not classes. Why does this return an runtime error?
if([NSObject respondsToSelector: @selector(init)] == YES )
[NSObject performSelector: @selector(init)];
Furthermore, since respondsToSelector is an instance method, why is it even possible to call it in the first place?
Short Answer:
NSObject
instance method (such as
respondsToSelector:
or init
) to the NSObject
class,
or to any class that inherits from NSObject
.[NSObject init]
is overridden in CoreFoundation and throws a runtime exception (for
binaries linked on OS X 10.6 or later).Long Answer:
Let's start with your last question:
Furthermore, since
respondsToSelector
is an instance method, why is it even possible to call it in the first place?
respondsToSelector:
is an instance method of the NSObject
protocol, to which
the NSObject
class conforms. Now the NSObject
class (and each its subclasses)
is an object and an instance of a subclass of the root class NSObject
.
This is explained and illustrated in Greg Parker's article [objc explain]: Classes and metaclasses (emphasis added):
More important is the superclass of a metaclass. The metaclass's superclass chain parallels the class's superclass chain, so class methods are inherited in parallel with instance methods. And the root metaclass's superclass is the root class, so each class object responds to the root class's instance methods. In the end, a class object is an instance of (a subclass of) the root class, just like any other object.
This explains why you can send respondsToSelector:
to the NSObject
class at all.
The return value is YES
if there is a class method with the given selector.
Here is another example:
NSString *path = [NSString performSelector:@selector(pathWithComponents:) withObject:@[@"foo", @"bar"]];
performSelector:withObject:
is an instance method of NSObject
, and you can send this
message to the NSString
class. In this case, the result is the same as
NSString *path = [NSString pathWithComponents:@[@"foo", @"bar"];
Now to your initial question:
Why does running
respondsToSelector
on NSObject with the selector "init" return 1 even though running[NSObject init]
gives a runtime error?
With the same reasoning as above, it must be possible to send the init
message to any
class that is derived from NSObject
.
Now NSObject
has a class method init
, which is documented to throw an
exception at runtime,
see http://opensource.apple.com/source/objc4/objc4-532.2/runtime/NSObject.mm:
// Replaced by CF (throws an NSException)
+ (id)init {
return (id)self;
}
This explains why
[NSObject respondsToSelector:@selector(init)] == YES
The comment in NSObject.mm states that +init
is overridden in CoreFoundation, and indeed,
when the exception is thrown, the stack backtrace is
(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
so this CoreFoundation method is responsible for the exception.
I do not know why the init
method is overridden in CoreFoundation
(instead of throwing an exception directly in NSObject
), and the replaced
method seems not be be part of the Open Source repository http://opensource.apple.com/source/CF/CF-855.14/
(at least I could not find it there).
But one interesting point may be that the observed behaviour has changed between OS releases. If
id x = [NSObject init];
is compiled on OS X 10.5, then it works and returns an NSObject
class object.
This works even if the binary has been compiled and linked on OS X 10.5 and runs on a later release such
as OS X 10.9.
This can also be seen from the assembly code of
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()
(from http://www.opensource.apple.com/source/CF/CF-476.14/CFPriv.h)
checks if the binary has been linked on OS X 10.6 or later, and throws an exception
in that case.
So calling init
on the class object must have been allowed in earlier OS releases,
and CoreFoundation still allows it in "old binaries" for backward compatibility.
respondsToSelector: is also a class method of NSProxy, and that method does take a class as the receiver. From the NSProxy docs,
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.
The NSObject's respondsToSelector
is internally implemented as:
+ (BOOL)respondsToSelector:(SEL)aSelector {
return class_respondsToSelector([self class], aSelector);
}
This only indicates if instances of NSObject respond to the mentioned selector. Various classes override this method to have their own implementation. For example, NSProxy overrides this method to give its own implementation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With