7

我已经阅读了使用 Objective-c 编程的苹果“消息传递”一章,并有几个关于 self 和 super 的问题。AFAIK 当编译器找到任何消息时,它会将其转换为带有两个隐藏参数的 objc_msgSend - 接收器、选择器和选择器的变量参数。例如[self test]将是这样的:

objc_msgSend(self, @selector(test));

如果接收者的调度表中没有方法实现,那么函数将尝试在超类中找到实现。super 只是编译器开始在当前对象的超类中搜索方法实现的标志,并且在文档中苹果表示,当编译器找到“super”时,它会将其转换为类似的内容:

struct objc_super mySuperClass = {
    self,
    [self superclass]
};
objc_msgSendSuper(&mySuperClass, @selector(forwardedMethod));

我制作了一个包含 3 个类的项目,每个类都继承自另一个类。

@interface FirstClass : NSObject
- (void)forwardMethod;
@end

@interface SecondClass : FirstClass
@end

@interface ThirdClass : SecondClass
@end

我在我的根视图控制器中创建了第三类的实例并调用了他的名为“forwardMethod”的方法。实施:

//First Class
- (void)forwardMethod {
   NSLog(@"Base class reached");
}

//SecondClass imp
- (void)forwardMethod {
   NSLog(@"second class");
   [super forwardMethod];
}

//ThirdClass imp
- (void)forwardMethod {
   NSLog(@"third class");
   [super forwardMethod];
}

一切正常。但后来我决定解释编译器:

//First Class
- (void)forwardMethod {
   NSLog(@"Base class reached");
}

//SecondClass imp
- (void)forwardMethod {
   NSLog(@"second class");
   struct objc_super mySuperClass = {
      self,
      [self superclass]
   };

   objc_msgSendSuper(&mySuperClass, @selector(forwardMethod));
}

//ThirdClass imp
- (void)forwardMethod {
   NSLog(@"third class");
    struct objc_super mySuperClass = {
        self,
        [self superclass]
    };

    objc_msgSendSuper(&mySuperClass, @selector(forwardMethod));
}

这导致对第二个类“forwardMethod”的递归调用。我使用 self 和 [self superclass] 在二等舱的“forwardMethod”中创建了一个结构,但 self 是三等舱,
我的超类将始终是“二等舱”。也许我做错了什么,但我怎样才能到达基类“转发方法”?

4

2 回答 2

6

注意:仅用于教育目的(这很好!),不要在生产代码中使用!

你也是最多的,只有一节课...

要了解为什么要进行递归,请考虑如何使用编译时运行时可用的信息找到超类。

在运行时, 的值self是对当前对象的引用,您可以使用它self来查找对象的类 - 在您的示例self中是类型的对象ThirdClass

现在,值self不会随着方法的调用而改变,因此在您的示例中,即使在FirstClass'sforwardMethod中的值self也是对 type 对象的引用ThirdClass。Soself使您能够找到对象的类型,但它不会告诉您当前在其继承链中执行方法的位置,因此它本身无法告诉您该链中的下一个类是什么。

所以考虑编译时间。编译时编译SecondClass器知道它是超类FirstClass,所以调用super是对方法的调用FirstClass (除了下面的警告)。因此,编译器可以使用self[FirstClass class]确定调用方法的运行时对象和开始搜索方法的编译时类(因为任何方法查找都是从类开始并沿着继承链进行的搜索,直到并找到实现)。因此,在您的示例代码中,您只是一种方法:

@implementation SecondClass

- (void)forwardMethod
{
   NSLog(@"second class");
   struct objc_super mySuperClass =
   {
      self,
      [FirstClass class]
   };

   objc_msgSendSuper(&mySuperClass, @selector(forwardMethod));
}

如果您使用它,您的代码将起作用。但...

......上面提到的警告。Objective-C 允许在运行时使用class swizzling更改继承链,因此编译器通常不能依赖编译时继承链来计算超类。那么它可以用来做什么呢?好吧,当为特定方法编译源代码时,它知道该方法所属的类,因此它可以将该方法类编译到代码中以查找超类并使用在运行时开始在下一个类中搜索方法的代码继承链。这实际上是编译器将为您的代码执行的操作,以防万一您使用了类 swizzling:

@implementation SecondClass

- (void)forwardMethod
{
   NSLog(@"second class");
   struct objc_super mySuperClass =
   {
      self,
      [SecondClass class]
   };

   objc_msgSendSuper2(&mySuperClass, @selector(forwardMethod));
}

请注意,[SecondClass class]编译objc_msgSendSuper2器传递编译时当前类( SecondCLass`objc_msgSendSuperSecondClass

玩得开心,但不要在一般代码中使用它(除非你有一个非常、非常、非常……非常好的理由;-))

于 2013-06-30T20:11:43.573 回答
4

在您的 SecondClass 中,此结构将填充与您的 ThirdClass 完全相同的内容:

   struct objc_super mySuperClass = {
      self,
      [self superclass]
   };

self在这两种情况下具有相同的值,因此,[self superclass]当从 ThirdClass 的实例调用时始终是 SecondClass(即使实际实现——代码——驻留在 SecondClass 中)。编译器发出的东西更神奇(objc_msgSendSuper 非常简单),因为它必须发出对类的引用,这样即使是摆姿势和/或 isa 指针操作之类的东西——糟糕的程序员,没有甜甜圈——仍然按预期工作。我很久很久都没有研究过细节,以确切地知道它是如何工作的。

运行时编译器的源代码可用。

于 2013-06-30T18:47:01.647 回答