为什么-respondsToSelector:在类名或类对象上使用实例方法?

时间:2012-03-12 05:14:06

标签: objective-c class class-method instance-methods respondstoselector

在目标C,4e,第9章,程序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:

由于-respondsToSelector:是实例方法,而不是类方法,为什么可以直接在Square类上使用它?

Q2:

这本书说你可以在这里使用Square而不是[Square class]。它只是一个特殊的捷径,还是背后有任何机制?

任何帮助都会非常感激!提前谢谢!

5 个答案:

答案 0 :(得分:4)

来自 Objective-C编程语言Objects, class, and Messaging

  

所有对象,类和实例都需要一个接口   运行时系统。类对象和实例都应该能够   对自己的能力进行反思并报告自己的能力   继承层次结构。这是NSObject类的省份   提供这个界面。

     

因此NSObject方法不必实现两次 - 一次   为实例提供运行时接口,并再次复制它   类对象的接口 - 类对象被赋予特殊的分配来执行根类中定义的实例方法。   当一个类对象收到一条消息时,它无法用a响应   类方法,运行时系统确定是否有根   可以响应的实例方法。 实例方法a   class对象可以执行的是那些在根类中定义,而只有在没有类方法可以完成工作时才会执行。

在这种情况下,NSObject是根类。由于NSObject个实例都符合NSObject protocol,其中定义了-respondsToSelector:,因此大多数类对象应该能够执行-respondsToSelector:

答案 1 :(得分:4)

Q1:

简单的答案是,除了类方法之外,您还可以在类对象上调用根类的任何实例方法(无论类的根类是什么,在本例中为NSObject)。

更复杂的答案是类对象是元类的实例。实例方法是实例上的方法,它们在类中定义; class方法是类对象的方法,它们在元类中定义。每个类都有自己的元类。元类的继承遵循其类的继承;即NSString的元类继承自NSObject的元类。最终,根类的元类继承自根类;即NSObject的元类继承自NSObject。这就是为什么所有NSObject的实例方法都可用于类对象的原因。

Q2:

[Square class]调用类方法+class(这与-class无关)。 +class本质上是一种身份方法,它只返回在其上调用的东西(就像-self);即如果foo是指向类对象的指针,则[foo class]foo相同。

所以+class似乎毫无用处;我们为什么用它?这是因为在Objective-C语言的语法中,类名不是有效的表达式(与Smalltalk不同)。所以你不能说id bar = Square;;那不会编译。作为语法中的特殊情况,允许在消息调用表达式中使用类名代替接收者,并将消息发送到类对象;即[Square something]。因此,如果要在任何其他表达式上下文中使用类对象,我们通过调用+class之类的标识方法以环形方式执行此操作; ie [Square class]是一个可以在任何表达式上下文中使用的表达式([Square self]也可以使用,但我们按惯例使用[Square class],这是不幸的,因为它与{{1 }});我们本来希望只使用-class,但不能使用该语言。

在您的情况下,它已经是消息调用表达式中的接收者,因此不必执行Square; [Square class]已在那里工作。

答案 2 :(得分:0)

// Q1:

因为-respondsToSelector:是一个实例方法,而不是一个类方法,为什么可以直接在Square类上使用它?//

您似乎有这样的想法,即无法从实例方法调用类方法(反之亦然)。相反,似乎是方法-respondsToSelector的目的是这样做,最有可能的方法是使用-class方法获取发送者的类,然后查询类是否响应选择器并返回YES或NO。在更局部化的示例中,请考虑以下事项:

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

在Objective-C中完全有效,前提是MyCurrentClass全部已分配并初始化。

// Q2:

这本书说你可以在这里使用Square而不是[Square class]。它只是一个特殊的捷径,或者这背后是否有任何机制?//

将-class发送给Class是完全多余的!它没什么意义,只是额外的不必要的代码。 -class只查询接收者的类,无论它是实例还是Class对象。

答案 3 :(得分:0)

Objective C运行时当前将一个类实现为某个其他类的实例对象。因此,一个类将响应某些实例方法。

答案 4 :(得分:-1)

真正的实现,直接来自NSObject.m就是这样:

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

现在,我不知道为什么PF_HELLO("")存在,但正如你所看到的,它确实在RUNTIME中提出了CLASS“嘿,你有一个方法,这个isa [实例]叫做aSelector吗? “

而且,在Objective-C中,类方法也属于实例,但是,优先级较低(在类方法之前调用与类方法同名的实例方法)。

Objective-C的动态类型的另一个方面是id类型实际上声明如下:

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

所以你的实例对象实际上是一个Class指针。这意味着您的-respondsToSelector消息也会转到实例类型的Class。在您的情况下,这意味着-respondsToSelector将转到objc_class FIRST。

现在在一个测试用例中,(直接来自libFoundation),我的答案总结如下:

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];