美文网首页
基于动态库加载研究ObjC的+load方法

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

作者: tom555cat | 来源:发表于2018-11-16 15:33 被阅读19次

    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方法

          本文链接:https://www.haomeiwen.com/subject/exipfqtx.html