ObjC运行时库中的+load方法在使用时有一系列规则,本文从源码层面分析这些规则背后的原理。+load有如下规则:
- class中或者category中实现的+load方法会调用并且只调用一次。
- class的+load方法会在class所有的superclass的+load方法之后调用。
- category的+load方法会在class的+load调用之后调用。
- +load方法中向与自身class处于同一个动态库的其他class发送消息,其他class的+load可能没有执行。
+load方法在什么时候调用?
程序在开始运行时dyld(动态库加载器)会加载系统动态库,自己使用的动态库和可执行文件,这些文件均是Mach-O文件。比如我们在一个动态库DyLibA的class中实现了+load方法。当dyld将所有Mach-O文件加载完成,包括我们的动态库,然后对所有的动态库和可执行文件进行初始化。这个流程在dyld的源码中调用流程如下所示:
// 初始化可执行文件
void initializeMainExecutable() {
...
// 初始化可执行文件依赖的动态库
sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]);
...
}
void ImageLoader::runInitializers(const LinkContext& context, InitializerTimingList& timingInfo)
{
...
processInitializers(context, thisThread, timingInfo, up);
...
}
void ImageLoader::processInitializers(const LinkContext& context, mach_port_t thisThread,
InitializerTimingList& timingInfo, ImageLoader::UninitedUpwards& images)
{
...
images.images[i]->recursiveInitialization(context, thisThread, images.images[i]->getPath(), timingInfo, ups);
...
}
void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
...
// 让objc知道我们即将初始化这个image
uint64_t t1 = mach_absolute_time();
fState = dyld_image_state_dependents_initialized;
oldState = fState;
context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
// 初始化这个image
bool hasInitializers = this->doInitialization(context);
// 告知其他人这个image已经初始化完成
fState = dyld_image_state_initialized;
oldState = fState;
context.notifySingle(dyld_image_state_initialized, this, NULL);
...
}
recursiveInitialization方法中对动态库的初始化是根据动态库的依赖来进行的,也就是初始化当前动态库时,当前动态库所依赖的其他动态库都已经初始化完成。在recursiveInitialization方法中要对一个库进行初始化时在context.notifySingle()方法中执行objc注册给dyld的回调函数load_images,而在load_images中就会执行我们实现的+load方法。objc在_objc_init()方法中向dyld注册了回调函数,如下所示:
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();
// objc向dyld注册回调函数
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
_dyld_objc_notify_register(&map_images, load_images, unmap_image)中的第一个参数map_images中会调用realizeClass()创建类对象,
中第二个参数load_images中就会调用我们现实的+load方法。
所以+load方法是在+load方法所在的动态库初始化的时候调用的。从上述源码流程来看,+load方法只会调用一次,从而解释了第一条规则:class中或者category中实现的+load方法会调用并且只调用一次。
+load在动态库内部的调用情况
ObjC注册给dyld的回调函数load_image的源码如下所示:
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
{
rwlock_writer_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
在其中,通过prepare_load_methods((const headerType *)mh)查找+load方法,然后通过call_load_methods()调用查找到的+load方法。
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertWriting();
// 获取non-lazy class
classref_t *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
// 递归,先获取class的super class的+load方法,然后获取自己的+load方法
schedule_class_load(remapClass(classlist[i]));
}
// 获取实现了+load的category
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());
// 获取category的+load方法
add_category_to_loadable_list(cat);
}
}
在prepare_load_methods中,通过_getObjc2NonlazyClassList获取non-lazy class,non-lazy class就是实现了+load方法的class,通过方法schedule_class_load将class的+load方法依据父类在前子类在后保存进了loadable_classes数组;通过_getObjc2NonlazyCategoryList获取了non-lazy category,最终category的+load保存进了loadable_categories数组。
- non-lazy class
实现了+load方法的class是non-lazy class,在ObjC向dyld注册回调的方法中:"_dyld_objc_notify_register(&map_images, load_images, unmap_image)",第一个回调map_images中就会对non-lazy class进执行realizeClass(cls)【对class进行第一次初始化】。而通过打断来看,回调map_imags是在回调load_images之前执行的,也就是说在执行+load方法时,对应的class都是经过第一次初始化的。
- lazy class
源码中或者注释中并没有具体地给出lazy class的定义,我们就把除了non-lazy class之外的class看做是lazy class。这类class的realizeClass(cls)是放在对该类发送消息时,如果该class没有经过初始化,那么会对这个class执行realizeClass(cls)
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
// 调用loadable_classes中所有的+load方法
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
// 调用loadable_categories中所有的+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;
}
通过call_load_methods()调用查找到的+load方法,从代码中可以看到先通过call_class_load()调用了loadable_classes中保存的所有的class的+load方法,然后才通过call_category_loads()调用了loadable_categories中保存的所有的category的+load方法。
所以规则2:class的+load方法会在class所有的superclass的+load方法之后调用和规则3:category的+load方法会在class的+load调用之后调用得到了保证。
对于规则4:+load方法中向与自身class处于同一个动态库的其他class发送消息,其他class的+load可能没有执行;通过call_load_methods方法,可以看到同一个动态库内的+load方法都是顺序执行,所以一个class的+load方法内使用到了动态库内部的另一个class,然而另一个class的+load方法是否执行了我们并不确定,所以使用另一个class的最终结果是不确定的。
但是如果当前动态库A中的+load方法中使用了另外一个动态库B中的class,那么动态库B一定会先于动态库A进行初始化,所以动态库B中的所有+load方法已经执行完毕,所以动态库A初始化时执行的+load方法中用到动态库B中的类都是完整初始化过的,不存在不确定的结果。
总结
首先从动态库加载的角度明确了+load方法执行的时机,是在+load所在的动态库/可执行文件初始化的时候执行的;在执行一个动态库的+load方法时,当前库依赖的其他动态库已经完成了初始化,也就是其相应的+load方法已经执行完成。
其次,从ObjC源码的角度明确了父类的+load方法先于子类的+load方法执行,class的+load方法先于category的+load方法执行。