Skip to content
ShenYj edited this page Mar 15, 2022 · 7 revisions

load

load 作为 Objective-C 中的一个方法,与其它方法有很大的不同。它只是一个在整个文件被加载到运行时,在 main 函数调用之前被 ObjC 运行时调用的钩子方法。

实现这个方法可以让我们在类加载的时候执行一些类相关的行为。子类的 load 方法会在它的所有父类的 load 方法之后执行,而分类的 load 方法会在它的主类的 load 方法之后执行。但是不同的类之间的 load 方法的调用顺序是不确定的。

  • 调用时机:

    • load方法会在runtime加载类、分类时调用, 每个类、分类的load,在程序运行过程中只调用一次
  • 调用顺序:

    • 先执行父类中的load方法
    • 先执行主类中的load方法
    • 再执行分类中的load方法,按着编译的反顺序,越后编译越先被执行

当有多个分类时,每个分类都重写原类中的一个方法时,那程序调用这个方法的时候就会按编译文件的顺序来判断,谁在最后就调用谁(可以通过项目设置中的Build Phases-->Compile Sources中调整)

原理:是将分类中的方法加入到了之前对象方法列表数组的前面了,所有找方法的时候会先找到分类中的方法

.

源码

libobjc_objc_init函数中调用了dyld_dyld_objc_notify_register函数,并将load_images传给dyld中以sNotifyObjCInit回调函数的方式存储起来

  • objc-os.mm_objc_init函数源码

    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();
        // 关于线程key的绑定,比如:线程数据的析构函数
        // 主要作用是: 本地线程池的初始化和析构
        tls_init();
        // 运行C++静态构造函数。在dyld调用我们的静态构造函数之前,libc 会调用 _objc_init()
        static_init();
        // runtime运行时环境初始化
        runtime_init();
        // libobjc异常处理系统初始化
        exception_init();
        // 缓存条件初始化
        cache_init();
        // 启动回调机制。通常不会做什么,因为所有的初始化都是惰性的
        _imp_implementationWithBlock_init();
        /*
        
        _dyld_objc_notify_register -- dyld 注册的地方
    
        - 仅供objc运行时使用
        - 注册处理程序,以便在映射、取消映射 和初始化objc镜像文件时使用,dyld将使用包含objc_image_info的镜像文件数组,回调 mapped 函数
        
        map_images:dyld将image镜像文件加载进内存时,会触发该函数
        load_images:dyld初始化image会触发该函数
        unmap_image:dyld将image移除时会触发该函数
        */
        _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    
    #if __OBJC2__
        didCallDyldNotifyRegister = true;
    #endif
    }
  • 通过objc源码中_objc_init源码实现,进入load_images的源码实现

    void load_images(const char *path __unused, const struct mach_header *mh)
    {
        if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
            didInitialAttachCategories = true;
            loadAllCategories();
        }
    
        // 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();
    }
    
    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) {
                call_class_loads();
            }
    
            // 2. Call category +loads ONCE
            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函数其核心是通过do-while循环调用类call_class_loads的函数或分类的call_category_loads函数

  • call_class_loads源码实现

    /***********************************************************************
    * 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, @selector(load));
        }
        
        // Destroy the detached list.
        if (classes) free(classes);
    }
  • call_category_loads源码实现

    /***********************************************************************
    * call_category_loads
    * Call some pending category +load methods.
    * The parent class of the +load-implementing categories has all of 
    *   its categories attached, in case some are lazily waiting for +initalize.
    * Don't call +load unless the parent class is connected.
    * If new categories become loadable, +load is NOT called, and they 
    *   are added to the end of the loadable list, and we return TRUE.
    * Return FALSE if no new categories became loadable.
    *
    * Called only by call_load_methods().
    **********************************************************************/
    static bool call_category_loads(void)
    {
        int i, shift;
        bool new_categories_added = NO;
        
        // Detach current loadable list.
        struct loadable_category *cats = loadable_categories;
        int used = loadable_categories_used;
        int allocated = loadable_categories_allocated;
        loadable_categories = nil;
        loadable_categories_allocated = 0;
        loadable_categories_used = 0;
    
        // Call all +loads for the detached list.
        for (i = 0; i < used; i++) {
            Category cat = cats[i].cat;
            load_method_t load_method = (load_method_t)cats[i].method;
            Class cls;
            if (!cat) continue;
    
            cls = _category_getClass(cat);
            if (cls  &&  cls->isLoadable()) {
                if (PrintLoading) {
                    _objc_inform("LOAD: +[%s(%s) load]\n", 
                                cls->nameForLogging(), 
                                _category_getName(cat));
                }
                (*load_method)(cls, @selector(load));
                cats[i].cat = nil;
            }
        }
    
        // Compact detached list (order-preserving)
        shift = 0;
        for (i = 0; i < used; i++) {
            if (cats[i].cat) {
                cats[i-shift] = cats[i];
            } else {
                shift++;
            }
        }
        used -= shift;
    
        // Copy any new +load candidates from the new list to the detached list.
        new_categories_added = (loadable_categories_used > 0);
        for (i = 0; i < loadable_categories_used; i++) {
            if (used == allocated) {
                allocated = allocated*2 + 16;
                cats = (struct loadable_category *)
                    realloc(cats, allocated *
                                    sizeof(struct loadable_category));
            }
            cats[used++] = loadable_categories[i];
        }
    
        // Destroy the new list.
        if (loadable_categories) free(loadable_categories);
    
        // Reattach the (now augmented) detached list. 
        // But if there's nothing left to load, destroy the list.
        if (used) {
            loadable_categories = cats;
            loadable_categories_used = used;
            loadable_categories_allocated = allocated;
        } else {
            if (cats) free(cats);
            loadable_categories = nil;
            loadable_categories_used = 0;
            loadable_categories_allocated = 0;
        }
    
        if (PrintLoading) {
            if (loadable_categories_used != 0) {
                _objc_inform("LOAD: %d categories still waiting for +load\n",
                            loadable_categories_used);
            }
        }
    
        return new_categories_added;
    }
    

    通过源码和注释得到被调用的正是类的+load方法

  • load的执行过程是:

    • _dyld_start
    • dyldbootstrap::start
    • dyld::_main
    • dyld::initializeMainExecutable
    • ImageLoader::runInitializers
    • ImageLoader::processInitializers
    • ImageLoader::recursiveInitialization
    • dyld::notifySingle(是一个回调处理)
    • sNotifyObjCInit
    • load_images(libobjc.A.dylib)
    • + [xxx load]

函数的执行过程

  • 懒加载类 (数据加载推迟到第一次消息的时候)

    • lookUpImpOrForward
    • realizeClassMaybeSwiftMaybeRelock
    • relizeClassWithoutSwift
    • methodizeClass
  • 非懒加载类(map_images的时候 加载所有数据)

    • readClass
    • _getObjc2NonlazyClassList
    • realizeClassWithoutSwift
    • methodizeClass

load方法的实现、实现方式对类加载到内存的影响

  • 懒加载类 与 懒加载分类

    其中realizeClassMaybeSwiftMaybeRelock是消息流程中慢速查找中有的函数,即在第一次调用消息时才有的函数

  • 非懒加载类 与 非懒加载分类

    map_images -> map_images_nolock -> _read_images -> readClass -> _getObjc2NonlazyClassList -> realizeClassWithoutSwift -> methodizeClass -> attachToClass, 此时的mlists是一维数组,然后走到load_images部分

    load_images --> loadAllCategories -> load_categories_nolock -> attachCategories -> attachLists,此时的mlists是二维数组

  • 非懒加载类 与 懒加载分类 即主类实现了+load方法,分类未实现+load方法

    类 和 分类的加载是在read_images就加载数据了, 其中data数据在编译时期就已经完成了

  • 懒加载类 与 非懒加载分类

    懒加载类 + 非懒加载分类的数据加载,只要分类实现了load,会迫使主类提前加载,即 主类 强行转换为 非懒加载类

    .

    主类 分类 类加载方式 类加载情况 分类加载情况
    load load non-lazy 类在map_images加载 分类在load_images加载
    load - non-lazy 类在map_images加载 分类方法已经通过mach-o读取到ro
    - load non-lazy 类在map_images加载 分类方法已经通过mach-o读取到ro
    - - lazy 类在第一次消息转发时加载 分类方法已经通过mach-o读取到ro

补充

  • 主类存在多个分类, 只要有一个分类实现了load方法,就会迫使主类提前加载,变成非懒加载类
  • 处理重名方法和排序时,先检查SEL的name(字符串地址),然后根据imp地址排序
  • 如果类和分类有同名方法
    • 如果是普通方法,则会调用分类中的重名方法
    • 如果是+load方法,则先调用类中的+load,在依次调用分类的+load

类的加载-分类的加载load方法调用后,加载一个类所有的工作都已经完成了

Getting Started

Social

Clone this wiki locally