我的对于super的调用过程当中,一些不同的理解

网上不少大神所解释的 super 调用逻辑,实际上好像并不能说得通。这里有个人一点点理解。c++


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

可是我的感受,他的解答和其余给出详细解释的大神们的回答,都有点不太能说得通。因此在这里说一下个人一些想法。函数

首先先把题目再放出来:ui

下面的代码输出什么?this

@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调试

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

当调用 [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查看

相关文章
相关标签/搜索