简体   繁体   中英

Why can -respondsToSelector: instance method be used on class name or class object?

In Programming in Objective C, 4e, Chapter 9, Program 9.3:

#import "Square.h"
int main (int argc, char * argv[])
{
   @autoreleasepool {
      Square *mySquare = [[Square alloc] init];
      ...
      // respondsTo:
      if ( [mySquare respondsToSelector: @selector (setSide:)] == YES )
         NSLog (@"mySquare responds to setSide: method");
      ...
      if ( [Square respondsToSelector: @selector (alloc)] == YES )
         NSLog (@"Square class responds to alloc method");
      ...
   }
   return 0;
}

Q1:

Since -respondsToSelector: is an instance method, not a class method, why would it be possible to use it on Square class directly?

Q2:

The book says you can use Square here instead of [Square class] . Is it only a exceptional shortcut, or is there any mechanism behind this?

Any help would be really appreciated! Thanks in advance!

Q1:

The simple answer is that, in addition to class methods, you can call any instances method of the root class (whatever the root class of your class is; in this case, NSObject ) on a class object.

The more complicated answer is that class objects are instances of metaclasses. Whereas instance methods are methods on an instance, which are defined in the class; class methods are methods on the class object, which are defined in the metaclass. Each class has its own metaclass. The inheritance of metaclasses follows that of their classes; ie NSString 's metaclass inherits from NSObject 's metaclass. Ultimately, the root class's metaclass inherits from the root class; ie NSObject 's metaclass inherits from NSObject . That is why all of NSObject's instance methods are available to class objects.

Q2:

[Square class] calls the class method +class (this is unrelated to -class ). +class is essentially an identity method that simply returns the thing called on it (just like -self ); ie if foo is a pointer to a class object, then [foo class] is the same as foo .

So +class seems pretty useless; why do we use it? That is because in the grammar of the Objective-C language, a class name is not a valid expression (unlike Smalltalk). So you cannot say id bar = Square; ; that would not compile. As a special case in the grammar, a class name is allowed in place of the receiver in a message call expression, and the message is sent to the class object; ie [Square something] . So if you want to use the class object in any other expression context, we do this in a roundabout way by calling an identity method like +class ; ie [Square class] is an expression that can be used in any expression context ( [Square self] would also work, but we use [Square class] by convention, which is unfortunate, since it is confused with -class ); we would have liked to just use Square , but can't due to the language.

In your case, it is already the receiver in a message call expression, so it is unnecessary to do [Square class] ; Square already works there.

From The Objective-C Programming Language , Objects, class, and Messaging ,

All objects, classes and instances alike, need an interface to the runtime system. Both class objects and instances should be able to introspect about their abilities and to report their place in the inheritance hierarchy. It's the province of the NSObject class to provide this interface.

So that NSObject methods don't have to be implemented twice—once to provide a runtime interface for instances and again to duplicate that interface for class objects— class objects are given special dispensation to perform instance methods defined in the root class . When a class object receives a message that it can't respond to with a class method, the runtime system determines whether there's a root instance method that can respond. The only instance methods that a class object can perform are those defined in the root class , and only if there's no class method that can do the job .

In this case, NSObject is the root class. As NSObject instances all comply with NSObject protocol , where -respondsToSelector: is defined, most class objects should be able to perform -respondsToSelector: .

//Q1:

Since -respondsToSelector: is an instance method, not a class method, why would it be possible to use it on Square class directly?//

You seem to have this notion that class methods cannot be called from instance methods (and vice versa). On the contrary, it would seem to be the intent of the method -respondsToSelector to do so, most likely by getting the class of the sender with the -class method, then querying if the class responds to the selector and returning YES or NO. In a more localized example, consider the following:

-(void)someInstanceMethod{
     [MyCurrentClass doClassMethod]; //basic equivalent of [self doClassMethid];
}

Is perfectly valid in Objective-C, provided MyCurrentClass is all alloc'd and init'ed.

//Q2:

The book says you can use Square here instead of [Square class]. Is it only a exceptional shortcut, or is there any mechanism behind this?//

It is completely redundant to send -class to a Class! It makes little sense, and is just extra unnecessary code. -class just queries for the reciever's class, no matter if it is an instance or Class object.

The Objective C run-time currently implements a class as an instance object of some other class. Thus a class will response to certain instance methods.

The real implementation, straight out of NSObject.m is as such:

- (BOOL)respondsToSelector:(SEL)aSelector {
        PF_HELLO("")
        return class_respondsToSelector( isa, aSelector );
}

Now, I have no idea why that PF_HELLO("") is there, but as you can see, it's literally ASKING the CLASS in the RUNTIME "Hey, do you have a method for this isa [instance] called aSelector?"

And, in Objective-C, class methods ALSO belong to instances, but, however, take lower precedence (the instance method of the same name as the class method is called before the class method).

Another aspect of Objective-C's Dynamic Typing is that the id type is in fact declared as follows:

typedef struct objc_class *Class;
typedef struct objc_object {
    Class isa;
} *id;

So your instance object is in fact, a Class pointer. This means your -respondsToSelector messages go to the Class of the instance type as well. In your case, it means that -respondsToSelector is going to the objc_class FIRST.

Now in a test case, (straight out of libFoundation), my answer would be summed up like this:

Test *tst = [Test new];

    fail_unless([tst respondsToSelector:@selector(testInstanceMethod)], "-[Test respondsToSelector:] returned NO for a valid instance method (testInstanceMethod).");
    fail_if([tst respondsToSelector:@selector(testClassMethod)], "-[Test respondsToSelector:] returned YES for a class method (testInstanceMethod).");
    fail_unless([Test respondsToSelector:@selector(testClassMethod)], "+[Test respondsToSelector:] returned NO for a valid class method (testClassMethod).");
    fail_if([Test respondsToSelector:@selector(testInstanceMethod)], "+[Test respondsToSelector:] returned YES for an instance method (testInstanceMethod).");
    fail_unless([tst respondsToSelector:@selector(init)], "-[Test respondsToSelector:] returned NO for an inherited instance method (-[NSObject init].");
    fail_unless([Test respondsToSelector:@selector(alloc)], "+[Test respondsToSelector:] returned NO for an inherited class method (+[NSObject alloc]).");

    [tst release];

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM