load和initialize的区别
调用机制
load方法的本质:直接执行函数指针
load方法是在运行时被执行的(main函数之前),其调用栈如下
_ load_images
//加载类和类别的load方法
└── load_images_nolock
//执行所有load方法
└── call_load_methods
而在load_images_nolock
方法中,则调用了prepare_load_methods
,其执行了两个方法:
_ prepare_load_methods
//先将需要执行 load 的 class 添加到一个全局列表里 (loadable_class)
└── schedule_class_load
//然后将需要执行 load 的 category 添加到另一个全局列表里(loadable_category)
└── add_category_to_loadable_list
而在shedule_class_load
方法中,确保先将父类添加到列表中。
static void schedule_class_load(class_t *cls)
{
assert(isRealized(cls)); // _read_images should realize
if (cls->data->flags & RW_LOADED) return;
//确保先将父类添加到全局列表里 (loadable_class)
class_t *supercls = getSuperclass(cls);
if (supercls) schedule_class_load(supercls);
//再将当前类添加到全局列表里 (loadable_class)
add_class_to_loadable_list((Class)cls);
changeInfo(cls, RW_LOADED, 0);
}
然后再执行call_load_methods
方法时
_ call_load_methods
//先遍历 loadable_classes 列表中的类,执行 load 方法。
└── call_class_loads
//然后再遍历 loadable_category 列表中的分类 ,执行 load 方法。
└── call_category_loads
而在call_class_loads
中,执行load方法的代码为
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
IMP load_method = classes[i].method;
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", _class_getName(cls));
}
(*load_method) ((id) cls, SEL_load);
}
由以上可知,load
方法,其实就是直接执行函数指针,不会执行消息发送objc_msgSend
那一套流程。子类、分类的load
方法不会覆盖父类的load
方法。
initialize方法的本质
在类、或者子类,接收到第一条消息之前被执行(如初始化)
initialize
方法最终通过objc_msgSend
来执行
initialize方法在main函数之后调用
如果一直没有使用类,则initialize
方法不会被调用
如果子类没有实现initialize
方法,则会调用父类的initialize
方法
源码分析:
__private_extern__ void _class_initialize(Class cls)
{
Class supercls;
BOOL reallyInitialize = NO;
// Get the real class from the metaclass. The superclass chain
// hangs off the real class only.
cls = _class_getNonMetaClass(cls);
// Make sure super is done initializing BEFORE beginning to initialize cls.
// See note about deadlock above.
supercls = _class_getSuperclass(cls);
if (supercls && !_class_isInitialized(supercls)) {
_class_initialize(supercls);
}
// Try to atomically set CLS_INITIALIZING.
monitor_enter(&classInitLock);
if (!_class_isInitialized(cls) && !_class_isInitializing(cls)) {
_class_setInitializing(cls);
reallyInitialize = YES;
}
monitor_exit(&classInitLock);
if (reallyInitialize) {
// We successfully set the CLS_INITIALIZING bit. Initialize the class.
// Record that we're initializing this class so we can message it.
_setThisThreadIsInitializingClass(cls);
// Send the +initialize message.
// Note that +initialize is sent to the superclass (again) if
// this class doesn't implement +initialize. 2157218
if (PrintInitializing) {
_objc_inform("INITIALIZE: calling +[%s initialize]",
_class_getName(cls));
}
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
if (PrintInitializing) {
_objc_inform("INITIALIZE: finished +[%s initialize]",
_class_getName(cls));
}
// Done initializing.
......
}
优先执行父类的initialize
方法;通过_class_getSupercass
取出父类,递归调用父类的initialize
方法;initialize
方法最终通过objc_msgSend
来执行的。
执行顺序
load
- 先调用类的
load
,再调用分类的load
- 先编译的类,优先调用
load
,调用子类的load
之前,会先调用父类的load
- 先编译的分类,优先调用
load
,顺序和Compile Sources
中顺序一致
场景1 :子类、父类、分类都实现load方法,调用情况
答:SuperClass->SubClass->CategoryClass
场景2 :子类、父类、分类中子类不实现load方法,调用情况
答:SuperClass->CategoryClass
场景3 :子类、父类、分类1、分类2都实现load方法,调用情况
答:SuperClass->SubClass->Category1Class->Category2Class
initialize
- 父类先于子类执行;(同load方法)
- 子类未实现,则会调用父类的
initialize
方法; - 分类实现了
initialize
方法,则会覆盖类中的initialize
方法(同category); - 存在多个分类,依赖
Compile Sources
中的顺序,执行最后一个分类的initialize
方法(同category);
场景1 :子类、父类都实现initialize方法,调用情况
答:SuperClass->SubClass
场景2 :子类、父类中子类不实现initialize方法,调用情况
答:SuperClass->SuperClass
(子类未实现,则会调用父类的initialize
,导致父类调用多次)
场景3:子类、父类、子类分类都实现initialize方法,调用情况
答:SuperClass->CategoryClass
(category中initialize方法覆盖其本类)
场景4:子类、父类、父类分类1、父类分类2都实现initialize方法,调用情况
答:CategoryClass->SubClass
(category中initialize方法根据Compile Sources排序执行最后一个)
使用场景
-
load
通常用于Method Swizzle
; -
initialize
可以用于初始化全局变量或静态变量;
注意:load和initialize方法内部使用了锁,因此他们是线程安全的。使用时避免阻塞线程,不要使用线程锁。
面试题
1、runtime中的交换方法在initialize中实现会有什么问题?
答:initialize
方法可能被其分类中的initialize方法覆盖,导致无法调用。