个人对于super的调用过程中,一些不一样的理解

栏目: Objective-C · 发布时间: 7年前

内容简介:个人对于super的调用过程中,一些不一样的理解

网上很多大神所解释的 super 调用逻辑,实际上好像并不能说得通。这里有我的一点点理解。

曾经有过一份特别好的 runtime 习题,在孙源大神的博客里 神经病院objc runtime入院考试 。题目非常难,也很深。其中的第一题,关于 super 关键字也是很刷新认知。

但是个人感觉,他的解答和其他给出详细解释的大神们的回答,都有点不太能说得通。所以在这里说一下我的一些想法。

首先先把题目再放出来:

下面的代码输出什么?

@implementation Son : Father
- (id)init {
    self = [super init];
    if (self) {
        NSLog(@"%@", NSStringFromClass([self class]));
        NSLog(@"%@", NSStringFromClass([super class]));
    }
    return self;
}
@end

答案是 Son / Son 。孙源大神给的一个简短解释是这样的:

因为super为编译器标示符,向super发送的消息被编译成 objc_msgSendSuper ,但仍以self作为reveiver。

这个解释显然不太够,看了之后依然有点懵逼。因为,这个没有说背后的理论证据,只给了一个结论性的理由。所以有另外一个大神,在他的文章中详细解释了,这也是目前流传最广的一份答案,ChunYeah大佬的博客 刨根问底 Objective-C Runtime(1)- Self & Super

贴一下他给出的解答中的关键部分:

当调用 [super class] 时,会转换成 objc_msgSendSuper 函数。第一步先构造  objc_super 结构体,结构体第一个成员就是  self 。第二个成员是  (id)class_getSuperclass(objc_getClass(“Son”)) , 实际该函数输出结果为  Father 。第二步是去  Father 这个类里去找 - (Class)class ,没有,然后去 NSObject 类去找,找到了。最后内部是使用  objc_msgSend(objc_super->receiver, @selector(class)) 去调用,此时已经和 [self class] 调用相同了,故上述输出结果仍然返回  Son

这个解释中,有一个地方的跨度非常大,就是:

最后内部是使用 objc_msgSend(objc_super->receiver, @selector(class)) 去调用

这个地方非常奇怪,就是 本来一直都是调用的 objc_msgSendSuper 函数,为啥最后换了调用另一个函数。 单从文章全文来看,似乎并没有做出解释。

我的猜测是,作者可能认为, objc_msgSendSuper 函数在向父类中去找实现的时候,最后一直找到了 NSObject 才找到了  - (Class)class 这个方法的实现,于是这个函数找到这个实现后,直接对这个类对象发送消息,变成了  objc_msgSend(objc_super->receiver, @selector(class))

也就是说,用 objc_msgSendSuper 找方法实现,找到了,再通过 objc_msgSend 给调用者发送消息,重新去沿着继承链再找方法实现。这么看来,岂不是很傻, 每一次调用父类方法的时候,都要遍历两遍继承链? 虽然 runtime 有很好的方法缓存机制,但是如此遍历,肯定不是一个好的设计。况且, objc_msgSendSuper 这个函数名就表示,就是已经在给父类发消息了,从代码的调用上,也可以证明。

下面就是调试代码:

@interface Father : NSObject
@end
  
@implementation Father
  
- (Class)class {
    return [NSValue class];
}

@end

@interface Son : Father
- (void)superClassName;
@end

@implementation Son
  
- (Class)class {
    return [NSNumber class];
}

- (void)superClassName {
    NSLog(@"Son's super Class is : %@", [super class]);
}

@end

int main(int argc, const char * argv[]) {
    Son *foo = [Son new];
    [foo superClassName];
    return 0;
}

这段代码就可以很好的反映,究竟是不是在调用 super 方法时是不是发送两次消息。

首先,在两个类的 class 重写方法中,打断点。

运行起来后,第一次的断点,是走 Father 类。 因为此时实际上是 objc_msgSendSuper 在发送消息。继续运行, Son 的断点是根本不会走的。 会直接输出 Son's super Class is : NSValue

所以,为什么当父类重写 - (Class)class 方法时,输出就是正确的?

按照ChunYeal大佬的说法,找到方法后会变成使用  objc_msgSend(objc_super->receiver, @selector(class)) 去调用,那么还是输出了 Son 的类才对啊。

所以,这个例子就证明了两点:

  1. 沿着父类找实现的过程只有一次。

  2. 使用 super 关键字究竟能不能得到正确结果,取决于是不是调用的方法是由 NSObject 来实现的。

所以,可以推断,在原先题目中,之所以 Son 取得 super class 的结果依然是 Son ,是因为 NSObject 的实现原因。

所以思路清晰了,重新捋一下。原来题目中整个过程应该是这样的

  1. Son 对象调用 super class 方法,编译器转换为 objc_msgSendSuper 函数发送 class 消息。

  2. 函数直接前往 Father 的实现中寻找,发现并没有实现。

  3. 继续沿着继承链,找到了 NSObject ,有 class 方法实现。

  4. 直接返回结果给调用者,这个结果就是 Son Class

所以,我们就需要明确, NSObject 类的 class 方法实现是怎样的原理。

所以,我们可以查看源文件 NSObject.mm 可以看到:

- (Class)class {
    return object_getClass(self);
}

继续看:

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

马上接近真相:

objc_object::getIsa() 
{
    if (!isTaggedPointer()) return ISA();

    uintptr_t ptr = (uintptr_t)this;
    if (isExtTaggedPointer()) {
        uintptr_t slot = 
            (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
        return objc_tag_ext_classes[slot];
    } else {
        uintptr_t slot = 
            (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
        return objc_tag_classes[slot];
    }
}

再往下已经不需要看了,因为我们能明确,实际上, NSObject- (Class)class 方法实际的本质是取得 isa 指针。 我们前面已经知道,当使用 super 关键字时,会转换成函数

objc_msgSendSuper(struct objc_super *super, SEL op, ...)

第一个参数是:

struct objc_super {
   __unsafe_unretained id receiver;
   __unsafe_unretained Class super_class;
};

使用 clang 重写文件,查看 NSLog(@"%@", NSStringFromClass([super class])); 对于的代码:

NSLog((NSString *)&__NSConstantStringImpl__var_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_a5cecc_mi_1, NSStringFromClass(((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){ (id)self, (id)class_getSuperclass(objc_getClass("Son")) }, sel_registerName("class"))));

所以可以得到结论,在向父类传递消息的时候,是去父类找实现,但是消息的接收者 receiver 依然是 self 。这一点,孙源大神讲得的确正确。但是关键就在于:

真正使得例子中返回的 class 为 Son 的原因,在于返回的是 selfisa

向上寻找的过程中,并不在乎是具体的哪一个父类实现了方法。而到到了 NSObject 中,也没有办法真的知道这个消息的接受者究竟是通过什么方式来访问 isa 的,所以,作为基类,就直接返回消息接受者的信息,这个在设计上就避免了很多不必要的问题。

所以,我认为,我的这个解释更具有合理性一些。

希望有不同意见的大神能指正其中的错误,更深入彻底的理解这个问题。

欢迎访问我的博客 http://suntao.me


以上所述就是小编给大家介绍的《个人对于super的调用过程中,一些不一样的理解》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

游戏测试精通

游戏测试精通

舒尔茨 / 周学毛 / 清华大学出版社 / 2007-9 / 48.00元

《游戏测试精通》来自3位在游戏测试领域都有着极其丰富经验的专业人员,是亚马逊“五星级”畅销书,也是国内第一本专业级游戏测试经典之作,不仅内容全面、实例丰富,而且讲解透彻、可读性强,并提供多个资源下载和技术支持站点。现如今,游戏产业发展迅猛,游戏测试已成为游戏产品、游戏软件、游戏程序设计与开发不可或缺的环节。《游戏测试精通》主要揭示了如何将软件测试的专业方法运用到游戏产业中,全面涵盖了游戏测试的基本......一起来看看 《游戏测试精通》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

html转js在线工具
html转js在线工具

html转js在线工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试