Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does [NSObject respondsToSelector:@selector(init)] return 1?

Tags:

objective-c

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?

like image 881
andrewg Avatar asked May 16 '14 05:05

andrewg


3 Answers

Short Answer:

  • You can send any 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.

like image 190
Martin R Avatar answered Dec 07 '22 18:12

Martin R


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.
like image 29
rdelmar Avatar answered Dec 07 '22 17:12

rdelmar


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.

like image 26
Shanti K Avatar answered Dec 07 '22 17:12

Shanti K