欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Runtime关于self和super的理解与解释

程序员文章站 2022-07-14 08:49:45
...

这个问题出自sunnyxx的一题:

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

结果是两个都输出Son。
对于这个问题的解释感觉各个地方都说的太高深,在这里讲一下自己的理解和猜测。

先来复习一下objc_msgSend的流程(伪码):
简单的说就是
1.先在对象自己的类对象中查找对应的方法指针IMP,找到了就执行IMP,并且第一个参数为对象自己self。
2.找不到就去父类对象中查找IMP,找到了就执行IMP,并且第一个参数为对象自己self
3.最后哪都找不到就进入消息转发。
注意无论最后调用的IMP是自己的还是父类的,传入的self都是对象自己。


id objc_msgSend(id self, SEL op, ...) {
    if (!self) return nil;
    IMP imp = class_getMethodImplementation(self->isa, SEL op);
    imp(self, op, ...); //调用这个函数,伪代码...
}

//查找IMP
IMP class_getMethodImplementation(Class cls, SEL sel) {
    if (!cls || !sel) return nil;
    IMP imp = lookUpImpOrNil(cls, sel);
    if (!imp) return _objc_msgForward; //_objc_msgForward 用于消息转发
    return imp;
}

IMP lookUpImpOrNil(Class cls, SEL sel) {
    if (!cls->initialize()) {
        _class_initialize(cls);
    }

    Class curClass = cls;
    IMP imp = nil;
    do { //先查缓存,缓存没有时重建,仍旧没有则向父类查询
        if (!curClass) break;
        if (!curClass->cache) fill_cache(cls, curClass);
        imp = cache_getImp(curClass, sel);
        if (imp) break;
    } while (curClass = curClass->superclass);

    return imp;
}

那么这道题的关键就在于对父类调用方法又是怎样一个流程呢
首先编译后不再是objc_msgSend,而是:

id objc_msgSendSuper(struct objc_super *super,SEL op)

super在编译后会被创建为一个objc_super结构体:

struct objc_super { id receiver; Class class; };

objc_super结构体创建的语句如下:
可以看出来结构体的的receiver成员就是对super发送消息的对象self,结构体的class成员是对象的父类对象

(__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Son")}

接下来是对objc_msgSendSuper内部流程的猜测:
1.objc_msgSendSuper从super结构体中获取到父类对象,在父类对象中查找IMP,如果找到了就调用IMP,传入第一个参数为super结构体中的receiver,既调用父类方法对象self。
2.如果在父类对象中查找不到,就去父类的父类对象中找,直到找到IMP,然后调用。
3.哪都找不到进入消息转发。

结合一开始的题目

  • [self class]流程:
    1.在自己的类对象中查找IMP,找不到。
    2.在父类对象中也找不到,然后追寻继承链一直找到NSObject,找到了,调用IMP并传入对象自己self为第一个参数,所以返回的结果是对象自己的类对象,也就是Son。

  • [super class]流程:
    1.直接在父类对象中查找IMP,找不到。
    2.然后追寻继承链一直找到NSObject,找到了,调用IMP并传入调用父类方法的对象为第一个参数(即还是传入self),所以返回的结果是调用父类对象自己的类对象,也就是Son。

  • 也就是说两者最后都是调用NSObject的class方法并传入self,返回self的类对象

拓展
结合这个思路,我们考虑

1 [self init]:
在自己的类对象中查找IMP,找到了,调用IMP并传入对象自己self为第一个参数

2 [super init]:
直接在父类对象中查找IMP,找到了,调用IMP并传入调用父类方法的对象self为第一个参数

总结
所以理解的重点就是到底找到的IMP是哪个类的,是自己的类对象中的IMP,还是父类对象中的IMP,还是更上层的某个父类对象中的IMP。
其次就是无论调用的是哪个IMP,对IMP传入的第一个参数对象都是一开始的调用者本身self。这也说明了为什么调用的IMP属于不同的类对象,但是IMP函数中所使用的成员变量永远都是调用者对象中的成员变量,因为虽然IMP储存在不同的类对象中,但是成员变量永远是储存在实例对象中。

PS:若有错误请指教,谢谢。

参考:
Objective-C Runtime
ios程序员6级考试
《招聘一个靠谱的 iOS》—参考答案(下)