+load
方法简介
+load
方法在这个类的文件被程序装载时调用。只要是在Compile Sources
中出现的文件总是会被装载,不管这个类是否被用到过。在一个程序开始运行之前(在main
函数开始执行之前),类文件开始被程序加载,+load
方法就会开始被执行;因此+load
方法总是在main
函数之前调用。
疑问:
-
objc
中类的+load
方法是怎么被调用的? - 为什么父类的
+load
比子类的+load
先执行? - 为什么宿主类的
+load
比分类的+load
先执行?
通过分析objc/runtime
源码消除以上疑问。
声明
@interface NSObject <NSObject>
...
+ (void)load;
...
@end
示例代码:
@interface Person : NSObject
@end
@implementation Person
+ (void)load {
NSLog(@"%s", __func__);
}
@end
@interface Woman : Person
@end
@implementation Woman
+ (void)load {
NSLog(@"%s", __func__);
}
@end
@implementation Woman (XYExtension)
+ (void)load {
NSLog(@"%s", __func__);
}
@end
@implementation Woman (XYExtension1)
+ (void)load {
NSLog(@"%s", __func__);
}
@end
输出结果:
2019-06-18 15:23:59.862941+0800 objc-test[17947:205876] +[Person load]
2019-06-18 15:23:59.863319+0800 objc-test[17947:205876] +[Woman load]
2019-06-18 15:24:02.215828+0800 objc-test[17947:205876] +[Woman(XYExtension1) load]
2019-06-18 15:24:03.911969+0800 objc-test[17947:205876] +[Woman(XYExtension) load]
调用顺序
根据上面的示例可以看到,宿主类的load
方法会先被调用,然后再调用分类的load
方法,其中宿主类中父类的load
方法先调用,非父子类型的按照Compile source
中的顺序调用,分类的load
方法根据Compile source
中的顺序调用。
-
load
方法的调用栈
从我们可以看到源码的位置开始,依次调用的顺序为:
load_images -> call_load_methods -> call_class_loads -> +load
这三个函数的源码分别为:
/***********************************************************************
* load_images
* Process +load in the given images which are being mapped in by dyld.
*
* Locking: write-locks runtimeLock and loadMethodLock
**********************************************************************/
extern bool hasLoadMethods(const headerType *mhdr);
extern void prepare_load_methods(const headerType *mhdr);
void
load_images(const char *path __unused, const struct mach_header *mh)
{
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
/***********************************************************************
* call_class_loads
* Call all pending class +load methods.
* If new classes become loadable, +load is NOT called for them.
*
* Called only by call_load_methods().
**********************************************************************/
static void call_class_loads(void)
{
int I;
// Detach current loadable list.
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
(*load_method)(cls, SEL_load);
}
// Destroy the detached list.
if (classes) free(classes);
}
/***********************************************************************
* call_class_loads
* Call all pending class +load methods.
* If new classes become loadable, +load is NOT called for them.
*
* Called only by call_load_methods().
**********************************************************************/
static void call_class_loads(void)
{
int I;
// Detach current loadable list.
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
(*load_method)(cls, SEL_load);
}
// Destroy the detached list.
if (classes) free(classes);
}
我们找到load_images
是在_objc_init
函数中被调用的。
_objc_init
的源码
/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
lock_init();
exception_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
我们在_objc_init
函数打断点,查看其调用栈,如下图
发现一切都是从一个叫
_dyld_start
的方法开始的
从load
调用栈和以上函数的实现,可以看到load
的执行过程:
程序启动运行时会依赖很多系统动态库,而系统动态库会通过dyld(动态加载器)(/usr/lib/dyld
)加载到内存中,这时候就执行了_dyld_start
,而在_dyld_start
中进行了动态库加载和初始化runtime的工作。
通过查看
_objc_init
的调用栈可以发现,在libSystem.B.dylib
的libSystem_initializer
函数中调用libdispatch.dylib
的libdispatch_init
函数初始化了libdispatch
,然后在libdispatch_init
又调用了_os_object_init
函数,最终调用了_objc_init
._objc_init
函数是runtime被加载后第一个执行的方法,它调用了_dyld_objc_notify_register
函数,这个函数注册dyld事件的监听。
_dyld_objc_notify_register
这个方法在苹果开源的dyld
里面可以找到,然后看到调用了dyld::registerObjCNotifiers
这个方法:
void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
// record functions to call
sNotifyObjCMapped = mapped;
sNotifyObjCInit = init;
sNotifyObjCUnmapped = unmapped;
// call 'mapped' function with all images mapped so far
// 第一次先触发一次ObjCMapped
try {
notifyBatchPartial(dyld_image_state_bound, true, NULL, false, true);
}
catch (const char* msg) {
// ignore request to abort during registration
}
}
-
load_images
函数的执行时机。
load_images
顾名思义就是调用+load
方法, 前面也看到了,它是在_objc_init
函数中注册的,这个方法在监听到dyld_image_states
改变为dyld_image_state_dependents_initialized
这个状态的时候会执行。即有新的镜像被加载到runtime
时,调用load_images
方法,并传入最新镜像的信息列表infoList
另外 +load
和 constructor
的执行时机是差不多的。
load_images
函数的函数主要分为两部分
load_images
函数线程安全的,它使用了mutex_locker
加锁。
1.准备所有需要调用+load
方法的类型。
- 在
load_images
函数中调用prepare_load_methods
对load
方法的调用进行准备(将需要调用的load
方法的类添加到一个列表中):
void prepare_load_methods(const headerType *mhdr)
{
size_t count, I;
runtimeLock.assertLocked();
classref_t *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[I];
Class cls = remapClass(cat->cls);
if (!cls) continue; // category for ignored weak-linked class
realizeClass(cls);
assert(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);
}
}
_getObjc2NonlazyClassList
函数中获取所有类的列表准备所有需要调用
+load
的类。
通过schedule_class_load
函数检查每一个类及其父类的+load
方法是否调用过,并将没有调用过+load
方法的类,通过add_class_to_loadable_list
函数放到loadable_classes
全局列表中,以便在下一步中调用这些类的+load
方法。值得注意的是将一个类添加到列表之前,会先检查其父类,符合时会先把父类添加到列表中再填加子类,最终这个列表决定了+load
方法的调用顺序。
2.调用准备好的类的+load
方法。
- 通过
call_load_methods
函数执行准备好的类的+load
方法
回到objc-rumtime-new.mm
中的load_images
函数中,在准备完+load
方法的类列表后,通过call_load_methods
函数调用这些类的+load
方法
我们先看下call_load_methods
函数的实现:
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
// 调用宿主类的`+load`方法
call_class_loads();
}
// 2. Call category +loads ONCE
// 调用分类的`+load`方法
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
从函数的实现可以看出,使用do {}while
循环遍历loadable_classes
类别,并让每个类执行call_class_loads
函数。然后执行call_category_loads
函数调用分类的+load
函数,这也是为什么宿主类的+load
方法比分类的+load
早调用的原因。