iOS developerruntime相关iOS程序猿

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

2016-12-17  本文已影响258人  6a948902fef0

这个问题出自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.哪都找不到进入消息转发。

结合一开始的题目

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

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

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

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

上一篇下一篇

猜你喜欢

热点阅读