「这是我参与2022首次更文挑战的第24天,活动详情查看:2022首次更文挑战」。
我们都知道initialize方法只有当类或子类第一次收到消息时才会调用。
我们在这个方法里打上断点来看看,比如我调用[Human new], 可以看到如下堆栈信息。\
从源码里我们可以看到,在
lookUpImpOrForward 里面,有初始化的判断。
if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
// runtimeLock may have been dropped but is now locked again
// If sel == initialize, class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}
一步步往下看 ,发现在 initializeNonMetaClass里 调用了callInitialize(cls);,而callInitialize实现如下
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
asm("");
}
可以看出在这里通过objc_msgSend的方式 对initialize进行了调用。
再来看看子类和父类同时实现initialize的情况
我这里Person类继承Human,都实现initialize,调用一些[Person new],按理来说应该只调用Person的initialize。
实际结果如下:
发现先调用了父类的initialize,再调用了子类的initialize。
在initializeNonMetaClass源码里我们可以找到原因所在
// Make sure super is done initializing BEFORE beginning to initialize cls.
// See note about deadlock above.
supercls = cls->superclass;
if (supercls && !supercls->isInitialized()) {
initializeNonMetaClass(supercls);
}
这里会确保在开始初始化cls之前,super已完成初始化。所以这里会调用父类的initialize,往后面走再调用子类的initialize。
如果在分类里实现initialize,那么分类的initialize则会‘‘覆盖’’本类的initialize。
那么最后总结一下+load和+initialize区别
| +load | +initialize | |
|---|---|---|
| 调用时机 | 在类和分类加入Runtime的时候调用 | 类或子类第一次收到消息时 |
| 调用方式 | 直接通过函数指针调用((*load_method)(cls, @selector(load))) | objc_msgSend |
| 调用顺序 | 父类->子类->分类(分类顺序看文件编译顺序) | 父类->子类(分类有实现,则父类->分类) |
| 本类未实现,是否调用父类 | 否 | 是 |
| 存在分类时 | 全都调用,晚于本类的调用 | ‘‘覆盖’’本类的方法,只调用分类的 |