【问题标题】:Why can -respondsToSelector: instance method be used on class name or class object?为什么 -respondsToSelector: 实例方法可以用于类名或类对象?
【发布时间】:2012-03-12 05:14:05
【问题描述】:

在 Objective 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;
}

第一季度:

既然-respondsToSelector:是实例方法,不是类方法,那为什么可以直接在Square类上使用呢?

第二季度:

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

任何帮助将不胜感激!提前致谢!

【问题讨论】:

  • 1.简短的回答是因为归根结底,它们都是选择器。 2. Square(大写)已经是一个类,class 方法字面上返回的是同一个对象。
  • Q2:Square 求值为 Square 的元类,它是一个表示 Square 类的对象。它是一个对象实例,因此您可以向它发送消息。将+class 发送到元类对象,返回自身。
  • 实际上,我上面的评论回答了你的两个问题:)
  • @CodaFi:感谢您的快速回复!但是,实例方法(例如 -respondsToSelector: )不应该只用于实例对象吗?
  • 好吧,你必须了解选择器是什么。无论是类方法还是实例方法,所有选择器都是 SEL 类型,这是该方法采用的参数之一。它只是检查您提供的 CLASS 是否响应它。无论如何,类方法都可以在实例方法中调用,所以这一点没有实际意义。

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


【解决方案1】:

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

所有对象、类和实例都需要一个接口 运行时系统。类对象和实例都应该能够 反思他们的能力并报告他们在 继承层次结构。这是 NSObject 类的领域 提供这个接口。

这样 NSObject 方法就不必实现两次——一次 为实例提供运行时接口并再次复制该接口 类对象的接口——类对象被赋予执行根类中定义的实例方法的特殊权限。 当一个类对象收到一条它不能用 类方法,运行时系统判断是否有根 可以响应的实例方法。 only 实例方法 类对象可以执行的是那些在根类中定义的,并且只有在没有可以完成这项工作的类方法时

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

【讨论】:

    【解决方案2】:

    第一季度:

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

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

    第二季度:

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

    所以+class 似乎毫无用处;我们为什么用它?这是因为在 Objective-C 语言的语法中,类名不是有效的表达式(与 Smalltalk 不同)。所以你不能说id bar = Square;;那不会编译。作为语法中的一个特例,允许在消息调用表达式中使用类名代替接收者,并将消息发送到类对象;即[Square something]。所以如果你想在任何其他表达式上下文中使用类对象,我们通过调用像+class这样的标识方法以迂回的方式做到这一点;即[Square class] 是一个可以在任何表达式上下文中使用的表达式([Square self] 也可以,但我们按照惯例使用[Square class],这是不幸的,因为它与-class 混淆了);我们本来希望只使用Square,但由于语言原因不能。

    在你的情况下,它已经是消息调用表达式中的接收者,所以不需要做[Square class]Square 已经在那里工作了。

    【讨论】:

      【解决方案3】:

      //Q1:

      既然-respondsToSelector: 是实例方法,不是类方法,为什么可以直接在Square类上使用呢?//

      您似乎认为类方法不能从实例方法中调用(反之亦然)。相反,这似乎是方法 -respondsToSelector 这样做的意图,很可能是通过使用 -class 方法获取发送者的类,然后查询该类是否响应选择器并返回 YES 或 NO。在更本地化的示例中,请考虑以下内容:

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

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

      //第二季度:

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

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

      【讨论】:

      • Q2应该是+class,否则调用不通。
      • 我还要注意类方法也属于实例,但不优先。
      • 您的 Q1 完全错过了这个问题,这就是为什么您可以在类对象上调用 respondsToSelector:
      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

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

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

        现在,我不知道为什么会出现 PF_HELLO(""),但正如你所看到的,它实际上是在 RUNTIME 中询问类“嘿,你有这个 isa [instance] 的方法,称为 aSelector 吗?”

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

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

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

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

        现在在一个测试用例中,(直接来自 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];
        

        【讨论】:

        • 我想知道 NSObject.m 代码是从哪里来的。不知道 Apple 何时开源了他们的 Foundation 框架。
        • 旧消息,GNUStep 和 Cocoatron。 GNUStep 基于 Mac OS X 基金会之前的 OpenStep(来自最初的 NextStep 基金会)。
        • 你的意思是所有的实例方法都可以对类对象执行吗?但是@ZhangChn 的回答说只有在根类中定义的实例方法才能在类对象上执行。关于这个矛盾的任何想法?谢谢!
        • 他解释的是我定义的优先级——对象也可以执行类方法。反过来也可以,这是我直接从 GNUStep 源代码和 libFoundation 测试中提供的。
        • “而且,在 Objective-C 中,类方法也属于实例,但优先级较低” 完全错误和错误。 “所以你的实例对象实际上是一个类指针。”什么?这表示对象的isa 字段是类指针,即如果您有id obj;,则obj->isa 是类指针;你甚至可以做(Class)*obj,这是一个类指针;但obj 不是类指针。 “这意味着您的 -respondsToSelector 消息也会转到实例类型的类。”绝对不是。
        最近更新 更多