基于动态库加载研究ObjC的+load方法

ObjC运行时库中的+load方法在使用时有一系列规则,本文从源码层面分析这些规则背后的原理。+load有如下规则:

  1. class中或者category中实现的+load方法会调用并且只调用一次。
  2. class的+load方法会在class所有的superclass的+load方法之后调用。
  3. category的+load方法会在class的+load调用之后调用。
  4. +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方法执行。

你可能感兴趣的:(基于动态库加载研究ObjC的+load方法)