Objective-C 的 RunTime(七):+load 与 +initialize 底层原理

Objective-C 中 +load 方法的底层原理

  • 源码分析: RunTime 向 dyld 注册 镜像初始化 的回调

    MachO && dyld(五)中,我们介绍了 dyld 加载和链接动态库的流程,整个流程可细分为 9 步 :
    ① 设置运行环境
    ② 加载系统共享缓存
    ③ 实例化主程序
    ④ 加载插入的动态库
    ⑤ 链接主程序
    ⑥ 链接插入的动态库
    ⑦ 执行弱符号绑定
    ⑧ 执行初始化方法
    ⑨ 查找 App 入口点并返回

    这里重点关注:⑧ 执行初始化方法,在这一步中:

    dyldImageLoaderMachO::doModInitFunctions 函数会去初始化 libSystem.dylib 库,并调用 libSystem.dylib 库的 libSystem_initializer 函数

    libSystem_initializer 函数会去初始化 libdispatch.dylib 库,并调用 libdispatch.dylib 库的 libdispatch_init 函数。接着 libdispatch_init 函数又会去调用 _os_object_init 函数

    最后 libdispatch.dylib 库的 _os_object_init 函数会去初始化 libobjc.dylib 库,并调用 libobjc.dylib 库的 _objc_init 函数

    整个函数调用栈如下图所示:
    _objc_init 函数调用栈
    其中,_objc_init 为 RunTime 的入口函数,用于进行一些初始化操作。其源码如下:

    // path: objc4-756.2/runtime/objc-os.mm
    // Runtime 的入口函数, 用于进行引导程序的初始化, 在库初始化之前由 libSystem 调用
    void _objc_init(void)
    {
        static bool initialized = false;
        if (initialized) return;
        initialized = true;
        
        // 修复程序会延迟初始化直到找到一个 objc-using 的镜像为止 ?
        environ_init();     // 初始化运行时的环境变量. 主要是读取影响 Runtime 的一些环境变量. 如果需要, 也可以输出环境变量的帮助提示 (在终端上直接输入命令 exprot OBJC_HELP=1)
        tls_init();         // 初始化 TLS (安全传输层协议). 这里执行的是关于线程 key 的绑定, 比如每条线程 数据的析构函数
        static_init();      // 初始化 C++ 的静态构造函数. 因为在 dyld 调用静态构造函数之前, libc 会调用 _objc_init(), 所以我们必须自己执行 C++ 静态构造函数. 这里只会初始化系统内置的 C++ 构造函数
        lock_init();        // 初始化锁. objc 的锁完全是采用 C++ 的锁的那一套逻辑
        exception_init();   // 初始化 libobjc 的异常处理系统
    
        // 向 dyld 注册镜像相关的回调通知(map_images:映射镜像, load_images:加载镜像/初始化镜像, unmap_image:卸载镜像)
        _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    }
    

    可以看到,在 RunTime 执行完所有初始化操作之后,调用了 _dyld_objc_notify_register 函数。_dyld_objc_notify_register 是 dyld 向 RunTime 提供的接口函数,RunTime 通过此函数向 dyld 注册回调。当 dyld(映射镜像、初始化镜像、卸载镜像)时,会通过 RunTime 注册的这些回调通知 RunTime 进行相应的处理

    在 dyld 初始化镜像时,会调用 RunTime 的 load_images 函数,用于处理由 dyld 映射的给定镜像中的 +load 方法
    load_images 函数总共做了 2 件事:

    1. 调用 prepare_load_methods 函数准备 +load 方法
    2. 调用 call_load_methods 函数执行 +load 方法
    // path: objc4-756.2/runtime/objc-runtime-new.mm
    void load_images(const char *path __unused, const struct mach_header *mh)
    {
        // 如果给定镜像中没有 +load 方法,则直接返回
        if (!hasLoadMethods((const headerType *)mh)) return;
    
        // 因为 load_images 函数由 dyld 进行回调,所以需要加锁来保证线程安全
        // 使用可递归锁是为了防止多次加锁造成死锁的情况
        recursive_mutex_locker_t lock(loadMethodLock);
    
        // 1.准备 +load 方法
        {
            mutex_locker_t lock2(runtimeLock);
            prepare_load_methods((const headerType *)mh);
        }
    
        // 2.执行 +load 方法
        call_load_methods();
    }
    

    在开始介绍 +load 方法的(准备与执行)流程之前,我们需要先了解几个关键的(数据类型与静态全局变量)的定义:

    // path: objc4-756.2/runtime/objc-loadmethod.mm
    
    // 用于标识 +load 方法实现的函数指针
    typedef void(*load_method_t)(id, SEL);
    
    // 用于标识可加载的类
    struct loadable_class {
        Class cls;      // 类对象
        IMP method;     // 类对象 cls 的 +load 方法的实现
    };
    
    // 用于标识可加载的分类
    struct loadable_category {
        Category cat;   // 分类对象
        IMP method;     // 分类对象 cat 的 +load 方法的实现
    };
    
    
    static struct loadable_class *loadable_classes = nil;       // struct loadable_class 类型的数组,用于存储要执行 +load 方法的类对象
    static int loadable_classes_used = 0;                       // 数组 loadable_classes 的已用容量
    static int loadable_classes_allocated = 0;                  // 数组 loadable_classes 的总容量
    
    
    static struct loadable_category *loadable_categories = nil; // struct loadable_category 类型的数组,用于存储要执行 +load 方法的分类对象
    static int loadable_categories_used = 0;                    // 数组 loadable_categories 的已用容量
    static int loadable_categories_allocated = 0;               // 数组 loadable_categories 的总容量
    
  • 源码分析:+load 方法的准备

    prepare_load_methods 函数用于准备要执行的 +load 方法:

    // path: objc4-756.2/runtime/objc-runtime-new.mm
    void prepare_load_methods(const headerType *mhdr)
    {
        size_t count, i;
        // 断言:已加锁(runtimeLock)
        runtimeLock.assertLocked();
    
        // 1.准备 Class 中要执行的 +load 方法
        // 1.1按编译顺序获取给定镜像中的 Class 数组
        classref_t *classlist = _getObjc2NonlazyClassList(mhdr, &count);
        for (i = 0; i < count; i++) {
            // 1.2遍历获取到的 classlist 数组
            //    按编译顺序依次调剂类对象 classlist[i],保证在静态全局数组 loadable_classes 中 父类在前 子类在后 的顺序
            //    因为 RunTime 可能会为类对象 classlist[i] 重新分配 struct objc_class,所以调用 remapClass 函数重新获取类对象 classlist[i] 的指针
            //    如果类对象 classlist[i] 使用弱链接,则 remapClass 函数将会返回 nil
            schedule_class_load(remapClass(classlist[i]));
        }
    
        // 2.准备 Category 中要执行的 +load 方法
        // 2.1按编译顺序获取给定镜像中的 Category 数组
        category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
        for (i = 0; i < count; i++) {
            category_t *cat = categorylist[i];
            // 因为 RunTime 可能会为类对象 cat->cls 重新分配 struct objc_class,所以调用 remapClass 函数重新获取类对象 cat->cls 的指针
            // 如果类对象 cat->cls 使用弱链接,则 remapClass 函数将会返回 nil
            Class cls = remapClass(cat->cls);
            // 忽略使用弱链接的宿主类对应的 Category
            if (!cls) continue;
            // 因为 Swift 的 Extension 和 Category 与 Objective-C 的 Extension 和 Category 不同,所以类对象 cls 不能为 Swift 类
            if (cls->isSwiftStable()) {
                _objc_fatal("Swift class extensions and categories on Swift "
                            "classes are not allowed to have +load methods");
            }
            // realizeClassWithoutSwift 函数用于实现类对象 cls,包括为类对象 cls 分配可读可写的 struct class_rw_t
            // realizeClassWithoutSwift 函数不执行任何 Swift 端的初始化操作
            // realizeClassWithoutSwift 函数将返回真实可用的 struct objc_class
            realizeClassWithoutSwift(cls);
            // 断言:类对象 cls 已经实现(即已经为类对象 cls 分配可读可写的 struct class_rw_t)
            assert(cls->ISA()->isRealized());
            // 2.2按编译顺序依次把要执行 +load 方法的分类对象 cat 添加到静态全局数组 loadable_categories 中
            add_category_to_loadable_list(cat);
        }
    }
    

    schedule_class_load 函数用于调剂类对象 cls,以保证 父类在前 子类在后 的顺序:

    // path: objc4-756.2/runtime/objc-runtime-new.mm
    static void schedule_class_load(Class cls)
    {
        // 如果类对象 cls 不存在,则直接返回
        if (!cls) return;
        // 断言:类对象 cls 已经实现,即 class_rw_t->flags 的 RW_REALIZED 标志位为真
        // 注意:_read_images 函数会实现类对象
        assert(cls->isRealized());
    
        // 如果类对象 cls 已经调用过 +load 方法,则直接返回
        // 即,如果 class_rw_t->flags 的 RW_LOADED 标志位为真,则直接返回
        if (cls->data()->flags & RW_LOADED) return;
    
        // 重点:递归调用 schedule_class_load 函数本身,确保未执行过 +load 方法的父类排在静态全局数组 loadable_classes 的前面
        schedule_class_load(cls->superclass);
    
        // 把要执行 +load 方法的类对象 cls 添加到静态全局数组 loadable_classes 中
        add_class_to_loadable_list(cls);
        // 将类对象 cls 的 class_rw_t->flags 的 RW_LOADED 标志位设置为真,表示类对象 cls 已经调用过 +load 方法
        cls->setInfo(RW_LOADED); 
    }
    

    add_class_to_loadable_list 函数用于把要执行 +load 方法的类对象 cls 添加到静态全局数组 loadable_classes 中:

    // path: objc4-756.2/runtime/objc-loadmethod.mm
    void add_class_to_loadable_list(Class cls)
    {
        IMP method;
    
        // 断言:已加锁(loadMethodLock)
        loadMethodLock.assertLocked();
    
        // 获取类对象 cls 的 +load 方法的实现(IMP)
        // 注意:不要被这个变量名所误导,在这里 method 代表的是 +load 方法的实现,而不是 +load 方法的结构体
        method = cls->getLoadMethod();
        // 如果类对象 cls 未实现 +load 方法,则直接返回
        // 重点:RunTime 只会执行实现了 +load 方法的类的 +load 方法,RunTime 不会执行未实现 +load 方法的类的 +load 方法
        if (!method) return;
        
        // 进行日志输出
        if (PrintLoading) {
            _objc_inform("LOAD: class '%s' scheduled for +load", 
                         cls->nameForLogging());
        }
        
        // 如果静态全局数组 loadable_classes 容量已满,则进行扩容
        // 静态全局数组 loadable_classes 用于存储要执行 +load 方法的类对象
        // 使用 realloc 函数在当前静态全局数组 loadable_classes 的基础上扩展数组的大小
        if (loadable_classes_used == loadable_classes_allocated) {
            loadable_classes_allocated = loadable_classes_allocated*2 + 16;
            loadable_classes = (struct loadable_class *)
                realloc(loadable_classes,
                                  loadable_classes_allocated *
                                  sizeof(struct loadable_class));
        }
        
        // 生产者-消费者模式:将类对象(cls)以及其对应的 +load 方法的实现(method)存入静态全局数组 loadable_classes 中
        loadable_classes[loadable_classes_used].cls = cls;
        loadable_classes[loadable_classes_used].method = method;
        loadable_classes_used++;
    }
    

    add_category_to_loadable_list 函数用于把要执行 +load 方法的分类 cat 添加到静态全局数组 loadable_categories

    // path: objc4-756.2/runtime/objc-loadmethod.mm
    void add_category_to_loadable_list(Category cat)
    {
        IMP method;
    
        // 断言:已加锁(loadMethodLock)
        loadMethodLock.assertLocked();
    
        // 获取分类对象 cat 的 +load 方法的实现(IMP)
        // 注意:不要被这个变量名所误导,在这里 method 代表的是 +load 方法的实现,而不是 +load 方法的结构体
        method = _category_getLoadMethod(cat);
    
        // 如果分类对象 cat 未实现 +load 方法,则直接返回
        // 重点:RunTime 只会执行实现了 +load 方法的分类的 +load 方法,RunTime 不会执行未实现 +load 方法的分类的 +load 方法
        if (!method) return;
    
        // 进行日志输出
        if (PrintLoading) {
            _objc_inform("LOAD: category '%s(%s)' scheduled for +load", 
                         _category_getClassName(cat), _category_getName(cat));
        }
        
        // 如果静态全局数组 loadable_categories 容量已满,则进行扩容
        // 静态全局数组 loadable_categories 用于存储要执行 +load 方法的分类对象
        // 使用 realloc 函数在当前静态全局数组 loadable_categories 的基础上扩展数组的大小
        if (loadable_categories_used == loadable_categories_allocated) {
            loadable_categories_allocated = loadable_categories_allocated*2 + 16;
            loadable_categories = (struct loadable_category *)
                realloc(loadable_categories,
                                  loadable_categories_allocated *
                                  sizeof(struct loadable_category));
        }
    
        // 生产者-消费者模式:将分类对象(cat)以及其对应的 +load 方法的实现(method)存入静态全局数组 loadable_categories 中
        loadable_categories[loadable_categories_used].cat = cat;
        loadable_categories[loadable_categories_used].method = method;
        loadable_categories_used++;
    }
    
  • 源码分析:+load 方法的执行

    call_load_methods 函数用于调用所有 Class 与所有 Category+load 方法:

    // path: objc4-756.2/runtime/objc-loadmethod.mm
    void call_load_methods(void)
    {
        static bool loading = NO;
        bool more_categories;
    
        // 断言:已加锁(loadMethodLock)
        loadMethodLock.assertLocked();
    
        if (loading) return;
        loading = YES;
    
        // 创建一个自动释放池(pool)
        void *pool = objc_autoreleasePoolPush();
    
        do {
            // 1.先执行 Class 的 +load 方法
            while (loadable_classes_used > 0) {
                call_class_loads();
            }
    
            // 2.再执行 Category 的 +load 方法
            more_categories = call_category_loads();
    
            // 3.如果在此期间新增了 Class 或者 Category 的 +load 方法,则继续执行之
        } while (loadable_classes_used > 0  ||  more_categories);
    
        // 将创建的自动释放池(pool)释放
        objc_autoreleasePoolPop(pool);
    
        loading = NO;
    }
    

    call_class_loads 函数用于执行 Class+load 方法的实现:

    // path: objc4-756.2/runtime/objc-loadmethod.mm
    static void call_class_loads(void)
    {
        int i;
        
        // 1.生产者-消费者模式:从静态全局数组 loadable_classes 中分离出要执行 +load 方法的 Class 的列表
        struct loadable_class *classes = loadable_classes;
        int used = loadable_classes_used;
        loadable_classes = nil;
        loadable_classes_allocated = 0;
        loadable_classes_used = 0;
        
        // 2.遍历分离出来的 classes 列表,执行 +load 方法
        for (i = 0; i < used; i++) {
            // 获取 classes[i] 中存储的类对象与 +load 方法的实现
            Class cls = classes[i].cls;
            load_method_t load_method = (load_method_t)classes[i].method;
            // 忽略 cls 为 nil 的 classes[i] 中的 +load 方法的实现
            if (!cls) continue;
            // 进行日志输出
            if (PrintLoading) {
                _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
            }
            // 通过函数地址(IMP)直接执行 +load 方法
            (*load_method)(cls, SEL_load);
        }
        
        // 3.释放分离出来的 classes 列表
        if (classes) free(classes);
    }
    

    call_category_loads 函数用于执行 Category+load 方法的实现:

    // path: objc4-756.2/runtime/objc-loadmethod.mm
    static bool call_category_loads(void)
    {
        int i, shift;
        bool new_categories_added = NO;
        
        // 1.生产者-消费者模式:从静态全局数组 loadable_categories 中分离出要执行 +load 方法的 Category 的列表
        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;
    
        // 2.遍历分离出来的 cats 列表,执行 +load 方法
        for (i = 0; i < used; i++) {
            // 获取 cats[i] 中存储的分类对象与 +load 方法的实现
            Category cat = cats[i].cat;
            load_method_t load_method = (load_method_t)cats[i].method;
            Class cls;
            // 忽略 cat 为 nil 的 cats[i] 中的 +load 方法的实现
            if (!cat) continue;
    
            // 虽然 call_load_methods 函数中的 do-while 循环能够在一定程度上保证 Class 的 +load 方法优先于 Category 的 +load 方法执行
            // 但是 call_load_methods 函数中的 do-while 循环不能完全保证 Class 的 +load 方法一定优先于 Category 的 +load 方法执行
            // 如果(保存 Category 的镜像)优先于(保存 Class 的镜像)被 dyld 加载,则 call_load_methods 函数中的 do-while 循环就不能够保证 +load 方法的执行顺序了
            // 因此在执行 Category 的 +load 方法之前,需要先检查其对应的宿主类:是否存在 && 是否可加载
            cls = _category_getClass(cat);
            if (cls  &&  cls->isLoadable()) {
                // 进行日志输出
                if (PrintLoading) {
                    _objc_inform("LOAD: +[%s(%s) load]\n", 
                                 cls->nameForLogging(), 
                                 _category_getName(cat));
                }
                // 通过函数地址(IMP)直接执行 +load 方法
                (*load_method)(cls, SEL_load);
                // 执行完 +load 方法之后,将其对应的分类 cat 置为 nil
                cats[i].cat = nil;
            }
        }
    
        // 3.将所有执行过 +load 方法的分类从 cats 列表中移除(cats 列表中剩余的分类保持顺序不变)
        shift = 0;
        for (i = 0; i < used; i++) {
            if (cats[i].cat) {
                cats[i-shift] = cats[i];
            } else {
                shift++;
            }
        }
        used -= shift;
    
        // 4.如果静态全局数组 loadable_categories 中有新增需要执行 +load 方法的分类对象
        //   则将静态全局数组 loadable_categories 中新增的分类对象拷贝到分离出来的 cats 列表中
        new_categories_added = (loadable_categories_used > 0);
        for (i = 0; i < loadable_categories_used; i++) {
            // 如果分离出来的 cats 列表的容量已满,则进行扩容
            // 使用 realloc 函数在分离出来的 cats 列表的基础上扩展数组的大小
            if (used == allocated) {
                allocated = allocated*2 + 16;
                cats = (struct loadable_category *)
                    realloc(cats, allocated *
                                      sizeof(struct loadable_category));
            }
            cats[used++] = loadable_categories[i];
        }
    
        // 5.释放静态全局数组 loadable_categories
        if (loadable_categories) free(loadable_categories);
        
        // 6.如果分离出来的 cats 列表中还存在需要执行 +load 方法的分类对象,则将分离出来的 cats 列表重新赋值给静态全局数组 loadable_categories
        //   如果分离出来的 cats 列表中不存在需要执行 +load 方法的分类对象,则释放分离出来的 cats 列表 并 清空静态全局数组 loadable_categories
        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;
        }
    
        // 7.进行日志输出
        if (PrintLoading) {
            if (loadable_categories_used != 0) {
                _objc_inform("LOAD: %d categories still waiting for +load\n",
                             loadable_categories_used);
            }
        }
    
        // 8.返回结果
        //   在执行本函数期间是否有新增需要执行 +load 方法的分类对象
        //   如果有,则返回 true。否则,返回 false
        return new_categories_added;
    }
    
  • 源码分析:如何判断镜像中是否存在要执行的 +load 方法

    load_images 函数中调用了 hasLoadMethods 函数用来判断给定的镜像中有无 +load 方法。hasLoadMethods 函数用于快速扫描给定的 MachO 镜像,并判断其中是否存在 +load 方法(不加锁):

    // path: objc4-756.2/runtime/objc-runtime-new.mm
    bool hasLoadMethods(const headerType *mhdr)
    {
        size_t count;
        // 如果给定的 MachO 镜像中存在非懒加载的 Class,则返回 true
        if (_getObjc2NonlazyClassList(mhdr, &count)  &&  count > 0) return true;
        // 如果给定的 MachO 镜像中存在非懒加载的 Category,则返回 true
        if (_getObjc2NonlazyCategoryList(mhdr, &count)  &&  count > 0) return true;
        // 如果以上两种情况都不是,则返回 false
        return false;
    }
    

    有趣的是,函数指针 _getObjc2NonlazyClassList_getObjc2NonlazyCategoryList 都是作为宏定义 GETSECT 的输入参数:

    // path: objc4-756.2/runtime/objc-file.mm
    // 宏定义 GETSECT 用于查询 MachO 的 Data 段中指定 Section 的记录
    // 这类似于 C++ 中的模板写法:通过宏定义 GETSECT 来处理泛型操作
    // param.name 函数名称(函数指针)
    // param.type 返回值类型
    // param.sectname 给定 MachO 文件 Data 段中 Section 的名称
    #define GETSECT(name, type, sectname)                                   \
        type *name(const headerType *mhdr, size_t *outCount) {              \
            return getDataSection<type>(mhdr, sectname, nil, outCount);     \
        }                                                                   \
        type *name(const header_info *hi, size_t *outCount) {               \
            return getDataSection<type>(hi->mhdr(), sectname, nil, outCount); \
        }
    
    // Section64(__DATA, __objc_nlclslist) 用于存储 Objective-C 中 Class 的 +load 方法
    // Section64(__DATA, __objc_nlcatlist) 用于存储 Objective-C 中 Category 的 +load 方法
    GETSECT(_getObjc2NonlazyClassList,    classref_t,      "__objc_nlclslist");
    GETSECT(_getObjc2NonlazyCategoryList, category_t *,    "__objc_nlcatlist");
    
  • +load 方法的原理总结

    ① +load 方法的调用方式:

    系统自动调用 +load 方法的方式为,通过函数地址直接调用(IMP
    开发者手动调用 +load 方法的方式为,通过消息机制间接调用(objc_msgSend

    ② 系统自动调用 +load 方法的时刻:

    当 dyld 初始化镜像时,会通过 RunTime 的 load_images 函数(准备和执行)镜像中所有 Class 和 Category 的 +load 方法。不管程序中有没有用到这些 Class 和 Category,只要镜像被初始化,这些 Class 和 Category 就都会被加载进内存并调用 +load 方法

    除非开发者手动调用,否则每个 Class 和 Category 的 +load 方法在程序运行期间只会被调用 1 次

    ③ 在同一个镜像中,系统自动调用 +load 方法的顺序

    先调用所有类对象的 +load 方法,按照编译顺序调用(先编译先调用,后编译后调用)。并且在调用子类对象的 +load 方法之前会先调用父类对象的 +load 方法

    再调用所有分类对象的 +load 方法,按照编译顺序调用(先编译先调用,后编译后调用)
    注意:分类对象与宿主类对象同名的普通方法是(后编译先调用)
    注意:分类对象 +load 方法的调用顺序只与分类对象的编译顺序有关,而与分类对象的继承关系无关

    系统只会自动调用实现了 +load 方法的 Class 与 Category 中的 +load 方法
    系统不会自动调用没有实现 +load 方法的 Class 与 Category 中的 +load 方法

    因为系统自动调用 +load 方法的方式为,通过函数地址直接调用(IMP
    所以 Class 与 Category 中的 +load 方法不会产生覆盖,都会被调用

    ④ 在同一个镜像中,开发者手动调用 +load 方法的顺序

    因为开发者手动调用 +load 方法的方式为,通过消息机制间接调用(objc_msgSend),所以:

    1. 如果子类未实现 +load 方法,则会调用父类的 +load 方法
    2. 如果宿主类与分类同时实现了 +load 方法,则会调用分类的 +load 方法
    3. 如果一个宿主类的多个分类同时实现了 +load 方法,则会调用最后参与编译的分类的 +load 方法

练习:Objective-C 中 +load 方法的调用顺序

新建一个名为 LoadMethodDemo 的 iOS - App,并创建以下 Class 和 Category:

Person 继承自 NSObject,有分类 Person(Male)Person(Female)
Chinese 继承自 Person,有分类 Chinese(Male)Chinese(Female)
Japanese 继承自 Person,有分类 Japanese(Male)Japanese(Female)

Dog 继承自 NSObject,有分类 Dog(Male)Dog(Female)
Husky 继承自 Dog,有分类 Husky(Male)Husky(Female)
Akita 继承自 Dog,有分类 Akita(Male)Akita(Female)

在上面创建的每个 Class 与 Category 中实现如下 +load 方法:

+(void)load {
	NSLog(@"%s", __func__);
}

创建 Student 继承自 Person,不实现 Student+load 方法

  • 在同一个镜像中,系统自动调用 +load 方法的顺序

    ① 以如下的编译顺序直接运行程序(不添加任何方法调用):

    对应的输出结果如下所示:

    ② 以如下的编译顺序直接运行程序(不添加任何方法调用):

    对应的输出结果如下所示:

    ③ 以如下的编译顺序直接运行程序(不添加任何方法调用):

    对应的输出结果如下所示:

    ④ 以如下的编译顺序直接运行程序(不添加任何方法调用):

    对应的输出结果如下所示:

  • 在同一个镜像中,开发者手动调用 +load 方法的顺序

    新增 -callLoadMethod 方法如下:

    -(void)callLoadMethod {
        [Person load];
        [Chinese load];
        [Japanese load];
        [Dog load];
        [Akita load];
        [Husky load];
        [Student load];
    }
    

    ① 以如下的编译顺序调用 -callLoadMethod 方法:

    对应的输出结果如下所示:

    ② 以如下的编译顺序调用 -callLoadMethod 方法:

    对应的输出结果如下所示:

Objective-C 中 +initialize 方法的底层原理

  • 源码分析:从消息发送流程到类对象的初始化

    Objective-C 的 RunTime(六):消息机制底层原理 中,我们介绍了 RunTime 消息机制的整个流程,知道了 Objective-C 的方法调用 [receiver selector] 在编译时都会被转换成 C 的消息发送 objc_msgSend(receiver, selector)

    在第一次向类对象(或者元类对象)发送消息之前,会先调用类对象的 +initialize 方法。这说明在 objc_msgSend 函数的内部调用流程中会判断是不是第一次向类对象(或者元类对象)发送消息,如果是的话,就会调用类对象的 +initialize 方法

    其中,从 objc_msgSend 函数开始的调用流程如下所示:

    objc_msgSend(汇编函数)
    CacheLookup(宏定义)
    CheckMiss(宏定义)
    _objc_msgSend_uncached(汇编函数)
    MethodTableLookup(宏定义)
    _class_lookupMethodAndLoadCache3(C++函数)
    lookUpImpOrForward(C++函数)
    ...

    其中第 ⑦ 步 lookUpImpOrForward 是标准的方法实现(IMP)查找函数,用于查找方法实现或者进行消息转发。lookUpImpOrForward 函数的主要功能有:

    1. 在当前类对象中进行不加锁的方法缓存查找
    2. 加锁 && 检查当前类对象是否已知
    3. 如果当前类对象没有实现(isRealized)或者初始化(isInitialized),则实现或者初始化当前类对象
    4. 尝试在当前类对象的方法缓存以及方法列表中查找方法实现
    5. 尝试在父类对象的方法缓存以及方法列表中查找方法实现
    6. 如果没有找到方法实现,则尝试进行动态方法解析
    7. 如果动态方法解析失败,则尝试进行消息转发
    8. 解锁、返回方法实现
    // path: objc4-756.2/runtime/objc-runtime-new.mm
    /*
    param.cls 要查找方法实现的类对象
    param.sel 要查找的方法实现对应的方法选择器
    param.inst 当前类 cls 或其子类的一个实例对象。如果 inst 未知,则可以传 nil。
    param.initialize 是否调用当前类 cls 的 +initialize 方法
    param.cache 是否查找当前类 cls 的方法缓存
    param.resolver 方法实现未找到时,是否尝试动态方法解析
    return 方法选择器 sel 对应的方法实现
    */
    IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver)
    {
        ...
    
        // 1.在当前类对象中进行不加锁的方法缓存查找,以提高方法缓存的使用性能
        ...
    
        // 2.加锁 && 检查当前类对象是否已知
        ...
    
        // 3.如果当前类对象没有实现(isRealized)或者初始化(isInitialized),则实现或者初始化当前类对象
        // 3.1如果当前类对象没有实现(isRealized),则实现当前类对象
        if (!cls->isRealized()) {
            cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
            // 在调用 realizeClassMaybeSwiftAndLeaveLocked 函数时,运行时锁有可能会被删除。但是现在运行时锁已经重新锁定
        }
    
        // 3.2如果当前类对象没有初始化(isInitialized),则初始化当前类对象
        if (initialize && !cls->isInitialized()) {
            cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
            // 在调用 initializeAndLeaveLocked 函数时,运行时锁有可能会被删除。但是现在运行时锁已经重新锁定
    
            // 如果参数 sel == initialize,则类对象 cls 的 +initialize 方法会被调用 2 次:
            // 1.initializeNonMetaClass 函数会调用类对象的 +initialize 方法
            // 2.在此过程执行完成之后,类对象将再次调用自己的 +initialize 方法
            // 当然,如果不是类对象主动调用自己的 +initialize 方法,则以上情况就不会发生
        }
    
        ...
    
        // 4.在当前类对象的方法缓存以及方法列表中查找方法实现
        ...
    
        // 5.尝试在父类对象的方法缓存以及方法列表中查找方法实现
        ...
    
        // 6.如果没有找到方法实现,则尝试进行动态方法解析
        ...
    
        // 7.如果动态方法解析失败,则尝试进行消息转发
        ...
    
        // 8.解锁、返回方法实现
        ...
    }
    

    lookUpImpOrForward 函数的第 3 步中:如果当前类对象没有实现(isRealized)或者初始化(isInitialized),则实现或者初始化当前类对象。分为 2 小步:
    3.1 如果当前类对象没有实现(isRealized),则实现当前类对象
    3.2 如果当前类对象没有初始化(isInitialized),则初始化当前类对象

    我们重点关注第 3.2 小步,RunTime 调用 +initialize 方法的流程由此开始

    注意:
    前面介绍的从 objc_msgSendlookUpImpOrForward 的函数调用栈,属于 RunTime 消息机制的一部分,不在本篇文章的讨论范围之内。对 RunTime 消息机制感兴趣的读者,可以参考 Objective-C 的 RunTime(六):消息机制底层原理

    本篇文章将从 initializeAndLeaveLocked 函数开始,介绍 RunTime 调用 +initialize 方法的流程

  • 源码分析:+initialize 方法的调用

    initializeAndLeaveLocked 函数底层调用了 initializeAndMaybeRelock 函数:

    // path: objc4-756.2/runtime/objc-runtime-new.mm
    /* 
    param.cls 要初始化的类对象
    param.obj 类 cls 的一个实例对象
    param.lock 运行时锁(runtimeLock)
    return 已经初始化的类对象
    */
    static Class initializeAndLeaveLocked(Class cls, id obj, mutex_t& lock)
    {
        return initializeAndMaybeRelock(cls, obj, lock, true);
    }
    

    initializeAndMaybeRelock 函数用于在调用类对象 cls+initialize 方法之前做相应的准备工作:

    // path: objc4-756.2/runtime/objc-runtime-new.mm
    /* 
    param.cls 要初始化的类对象
    param.inst 类 cls 的一个实例对象(非空的 inst 有助于提升函数的性能)
    param.lock 运行时锁(runtimeLock)
    param.leaveLocked 函数退出时,是否保留运行时锁。true - 保留运行时锁,false - 删除运行时锁
                      此函数的调用者必须持有运行时锁,此函数有可能会删除运行时锁
    return 已经初始化的类对象
    */
    static Class initializeAndMaybeRelock(Class cls, id inst, mutex_t& lock, bool leaveLocked)
    {
        // 断言:已加锁(runtimeLock)
        lock.assertLocked();
        // 断言:类对象 cls 已经实现
        assert(cls->isRealized());
    
        // 1.如果类对象 cls 已经初始化,则直接返回
        if (cls->isInitialized()) {
            if (!leaveLocked) lock.unlock();
            return cls;
        }
    
        // 2.获取参数 cls 对应的普通类对象
        // 如果 lookUpImpOrForward 函数执行的是对象方法的消息发送流程,则参数 cls 为类对象
        // 如果 lookUpImpOrForward 函数执行的是类方法的消息发送流程,则参数 cls 为元类对象
        // 因为 +initialize 是发送给类对象的消息,所以调用 getMaybeUnrealizedNonMetaClass 函数,通过参数 cls 获取普通类对象 nonmeta
        Class nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
    
        // 3.如果类对象 nonmeta 未实现,则实现类对象 nonmeta
        if (nonmeta->isRealized()) {
            lock.unlock();
        } else {
            nonmeta = realizeClassMaybeSwiftAndUnlock(nonmeta, lock);
            cls = object_getClass(nonmeta);
        }
    
        // 断言:类对象 nonmeta 已经实现
        assert(nonmeta->isRealized());
        
        // 4.调用类对象 nonmeta 的 +initialize 方法
        initializeNonMetaClass(nonmeta);
    
        // 4.判断是否保留运行时锁
        if (leaveLocked) runtimeLock.lock();
        
        // 5.返回已经初始化的类对象 cls
        return cls;
    }
    

    initializeNonMetaClass 函数用于调用未初始化的类对象 cls+initialize 方法,此函数会优先调用未初始化的父类对象的 +initialize 方法,然后再调用未初始化的子类对象的 +initialize 方法:

    // path: objc4-756.2/runtime/objc-initialize.mm
    // param.cls 要初始化的类对象
    void initializeNonMetaClass(Class cls)
    {
        // 1.断言:参数 cls 是类对象
        assert(!cls->isMetaClass());
    
        Class supercls;
        bool reallyInitialize = NO;
    
        // 2.如果父类存在并且没有进行过初始化,则优先初始化父类
        //   递归调用 initializeNonMetaClass 函数本身,确保优先调用父类的 +initialize 方法,然后再调用子类的 +initialize 方法
        supercls = cls->superclass;
        if (supercls  &&  !supercls->isInitialized()) {
            initializeNonMetaClass(supercls);
        }
        
        // 3.如果类对象 cls 还未进行过初始化,则将类对象设置为初始化中。即,将类对象 cls 的 RW_INITIALIZING 标志位设置为真
        //
        // 类对象 cls 的初始化过程,存在 3 种状态:
        //      ① 类对象 cls 还未进行过初始化,此时 cls->isInitialized() == false && cls->isInitializing() == false
        //         对应地,标志位 RW_INITIALIZED == 0 && 标志位 RW_INITIALIZING == 0
        //      ② 类对象 cls 处于初始化过程中,此时 cls->isInitialized() == false && cls->isInitializing() == true
        //         对应地,标志位 RW_INITIALIZED == 0 && 标志位 RW_INITIALIZING == 1
        //      ③ 类对象 cls 已经初始化完成,此时 cls->isInitialized() == ture && cls->isInitializing() == false
        //         对应地,标志位 RW_INITIALIZED == 1 && 标志位 RW_INITIALIZING == 0
        //
        // 因为本函数存在多线程调用,所以当一个类对象 cls 处于初始化状态(Initializing)时,会出现两种可能:
        //      ① 类对象 cls 的初始化状态(Initializing)是由本线程的本次调用所触发,本次调用需要完成对类对象 cls 的初始化。对应 bool 型变量 reallyInitialize == YES
        //      ② 类对象 cls 的初始化状态(Initializing)不是由本线程的本次调用所触发,本次调用不需要完成对类对象 cls 的初始化。对应 bool 型变量 reallyInitialize == NO
        {
            monitor_locker_t lock(classInitLock);
            if (!cls->isInitialized() && !cls->isInitializing()) {
                cls->setInitializing();
                reallyInitialize = YES;
            }
        }
        
        // 4.1如果类对象 cls 正在初始化,并且类对象 cls 的初始化状态(Initializing)是由本线程的本次调用所触发,则本次调用需要完成对类对象 cls 的初始化
        if (reallyInitialize) {
            // 记录当前线程正在初始化给定的类对象 cls。当前线程将被允许向类对象 cls 发送消息
            // 其他线程如果要向类对象 cls 发送消息,则需要等待当前线程初始化完成类对象 cls 之后
            _setThisThreadIsInitializingClass(cls);
    
            // 处理在 fork 出来的子线程中调用 +initialize 方法时出现的死锁问题
            if (MultithreadedForkChild) {
                performForkChildInitialize(cls, supercls);
                return;
            }
            
            // 进行日志输出
            if (PrintInitializing) {
                _objc_inform("INITIALIZE: thread %p: calling +[%s initialize]",
                             pthread_self(), cls->nameForLogging());
            }
    
            // 调用 +initialize 方法
            // 注意:
            // 如果当前类对象 cls 没有实现 +initialize 方法,则会调用父类对象的 +initialize 方法
            // 即使在调用 +initialize 方法的过程中抛出了异常,也会被视为进行了一个完整且成功的 +initialize 方法调用
            @try
            {
                // 调用类对象 cls 的 +initialize 方法
                callInitialize(cls);
    
                // 进行日志输出
                if (PrintInitializing) {
                    _objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
                                 pthread_self(), cls->nameForLogging());
                }
            }
            @catch (...) {
                // 进行日志输出
                if (PrintInitializing) {
                    _objc_inform("INITIALIZE: thread %p: +[%s initialize] "
                                 "threw an exception",
                                 pthread_self(), cls->nameForLogging());
                }
                @throw;
            }
            @finally
            {
                // 完成 +initialize 方法的调用:将类对象 cls 设置为已经初始化完成(Initialized),并在稍后通知等待的线程或者队列
                lockAndFinishInitializing(cls, supercls);
            }
            return;
        }
        
        // 4.2如果类对象 cls 正在初始化(Initializing),并且类对象 cls 的初始化状态不是由本线程的本次调用所触发,则本次调用不需要完成对类对象 cls 的初始化
        else if (cls->isInitializing()) {
            if (_thisThreadIsInitializingClass(cls)) {
                return;
            } else if (!MultithreadedForkChild) {
                waitForInitializeToComplete(cls);
                return;
            } else {
                _setThisThreadIsInitializingClass(cls);
                performForkChildInitialize(cls, supercls);
            }
        }
        
        // 4.3如果类对象 cls 已经初始化过了(Initialized),则直接返回
        else if (cls->isInitialized()) {
            return;
        }
        
        // 4.4处理其他意外情况:报错并杀死当前进程
        else {
            _objc_fatal("thread-safe class init in objc runtime is buggy!");
        }
    }
    

    callInitialize 函数用于通过消息发送机制 objc_msgSend 向类对象 cls 发送 initialize 消息:

    // path: objc4-756.2/runtime/objc-initialize.mm
    // param.cls 要初始化的类对象
    void callInitialize(Class cls)
    {
    	// (RunTime 自动调用 +initialize 方法的方式)与(开发者手动调用普通方法的方式是一样的),走的都是消息发送的流程
        ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
        asm("");
    }
    

    为了在堆栈跟踪时提供有用的信息,将 callInitialize 在函数调用栈中重命名为 _CALLING_SOME_+initialize_METHOD
    所以我们在 +initialize 方法的函数调用栈中,实际上看到的 +initialize 方法的调用者为 _CALLING_SOME_+initialize_METHOD

    // path: objc4-756.2/runtime/objc-initialize.mm
    OBJC_EXTERN __attribute__((noinline, used, visibility("hidden")))
    void callInitialize(Class cls)
        asm("_CALLING_SOME_+initialize_METHOD");
    
  • 源码分析:类对象的实现(realize)过程

    接下来我们回到 lookUpImpOrForward 函数中,来探究一下类对象 cls 的实现过程。我们重点关注第 3.1 小步,RunTime 实现类对象 cls 的流程由此开始

    realizeClassMaybeSwiftAndLeaveLocked 函数底层调用了 realizeClassMaybeSwiftMaybeRelock 函数:

    // path: objc4-756.2/runtime/objc-runtime-new.mm
    /*
    param.cls 要实现的类对象
    param.lock 运行时锁(runtimeLock)
    return 已实现的类对象
    */
    static Class realizeClassMaybeSwiftAndLeaveLocked(Class cls, mutex_t& lock)
    {
        return realizeClassMaybeSwiftMaybeRelock(cls, lock, true);
    }
    

    realizeClassMaybeSwiftMaybeRelock 函数用于判断类对象 cls 是 Objective-C 类还是 Swift 类,然后调用不同的函数去实现类对象 cls

    // path: objc4-756.2/runtime/objc-runtime-new.mm
    /*
    param.cls 要实现的类对象
    param.lock 运行时锁(runtimeLock)
    param.leaveLocked 函数退出时,是否保留运行时锁。true - 保留运行时锁,false - 删除运行时锁
                      此函数的调用者必须持有运行时锁,此函数有可能会删除运行时锁
    return 已实现的类对象
    */
    static Class realizeClassMaybeSwiftMaybeRelock(Class cls, mutex_t& lock, bool leaveLocked)
    {
        // 断言:已加锁(runtimeLock)
        lock.assertLocked();
    
        if (!cls->isSwiftStable_ButAllowLegacyForNow()) {
            // 如果类对象 cls 是一个 Objective-C 类,则需要持有运行时锁(runtimeLock)并调用 realizeClassWithoutSwift 函数来实现它
            realizeClassWithoutSwift(cls);
            if (!leaveLocked) lock.unlock();
        } else {
            // 如果类对象 cls 是一个 Swift 类,则需要删除运行时锁(runtimeLock)并调用 Swift 的 RunTime 来实现它
            lock.unlock();
            cls = realizeSwiftClass(cls);
            assert(cls->isRealized());
            if (leaveLocked) lock.lock();
        }
    
        return cls;
    }
    

    realizeClassWithoutSwift 函数用于实现类对象 cls,包括为类对象 cls 分配可读可写的 struct class_rw_t
    realizeClassWithoutSwift 函数不执行任何 Swift 端的初始化操作
    realizeClassWithoutSwift 函数将返回真实可用的 struct objc_class
    运行时锁(runtimeLock)必须由调用者持有

    // path: objc4-756.2/runtime/objc-runtime-new.mm
    // param.cls 要实现的类对象
    // return 已经实现了的类对象
    static Class realizeClassWithoutSwift(Class cls)
    {
        // 断言:已加锁(runtimeLock)
        runtimeLock.assertLocked();
    
        const class_ro_t *ro;
        class_rw_t *rw;
        Class supercls;
        Class metacls;
        bool isMeta;
    
        if (!cls) return nil;               // 确保类对象 cls 不为 nil
        if (cls->isRealized()) return cls;  // 确保类对象 cls 未实现
        assert(cls == remapClass(cls));     // 确保类对象 cls 未重新分配内存
    
        ro = (const class_ro_t *)cls->data();
        if (ro->flags & RO_FUTURE) {
            // 如果类对象 cls 是 Future 类,则说明类对象 cls 的 struct class_rw_t 已经分配好了
            rw = cls->data();
            ro = cls->data()->ro;
            cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
        } else {
            // 如果类对象 cls 是普通类,则为类对象 cls 分配可读可写的 struct class_rw_t
            rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
            rw->ro = ro;
            rw->flags = RW_REALIZED|RW_REALIZING;
            cls->setData(rw);
        }
    
        // 类对象 cls 是否为元类
        isMeta = ro->flags & RO_META;
    
        // 设置类对象 cls 的版本(old runtime went up to 6)
        rw->version = isMeta ? 7 : 0;
    
        // 为类对象 cls 选择一个索引。如果没有更多的索引,则禁用类对象 cls 的 isa 指针优化
        cls->chooseClassArrayIndex();
    
        // 进行日志输出
        if (PrintConnecting) {
            _objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
                         cls->nameForLogging(), isMeta ? " (meta)" : "", 
                         (void*)cls, ro, cls->classArrayIndex(),
                         cls->isSwiftStable() ? "(swift)" : "",
                         cls->isSwiftLegacy() ? "(pre-stable swift)" : "");
        }
    
        // 如果类对象 cls 的父类对象和元类对象还未实现,则实现类对象 cls 的父类对象和元类对象
        supercls = realizeClassWithoutSwift(remapClass(cls->superclass));
        metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));
    
    // isa 指针优化 == non-pointer isa
    // 如果开启了 isa 指针优化,则根据情况禁用某些类或者某些平台上的 isa 指针优化
    #if SUPPORT_NONPOINTER_ISA
    
        bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
        bool rawIsaIsInherited = false;
        static bool hackedDispatch = false;
    
        if (DisableNonpointerIsa) {
            // 如果设置了环境变量 OBJC_DISABLE_NONPOINTER_ISA = ture 或者 app 的 SDK 版本过低,则会禁用类对象 cls 的 isa 指针优化
            instancesRequireRawIsa = true;
        }
        else if (!hackedDispatch  &&  !(ro->flags & RO_META)  && 0 == strcmp(ro->name, "OS_object"))
        {
            // 为了防止黑客攻击,禁用 libdispatch 库的 OS_object 类对象的 isa 指针优化
            hackedDispatch = true;
            instancesRequireRawIsa = true;
        }
        else if (supercls  &&  supercls->superclass  && supercls->instancesRequireRawIsa())
        {
            // 如果类对象 cls 的父类对象禁用了 isa 指针优化,则会禁用类对象 cls 的 isa 指针优化
            instancesRequireRawIsa = true;
            rawIsaIsInherited = true;
        }
        
        if (instancesRequireRawIsa) {
            cls->setInstancesRequireRawIsa(rawIsaIsInherited);
        }
    
    #endif
    
        // 更新类对象 cls 的父类对象和元类对象
        cls->superclass = supercls;
        cls->initClassIsa(metacls);
    
        // 调整类对象 cls 的成员变量的 偏移量与布局
        // 这可能会重新分配类对象 cls 的 struct class_ro_t,导致 cls->ro 变量更新
        if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);
    
        // 设置类 cls 的实例对象的大小
        cls->setInstanceSize(ro->instanceSize);
    
        // 处理类对象 cls 有 .cxx_construct 函数实现的情况
        if (ro->flags & RO_HAS_CXX_STRUCTORS) {
            cls->setHasCxxDtor();
            if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
                cls->setHasCxxCtor();
            }
        }
        
        // 处理类对象本身或者其父类对象 禁止添加 关联对象的情况
        if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
            (supercls && supercls->forbidsAssociatedObjects()))
        {
            rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
        }
    
        // 将类对象 cls 添加到其父类的子类列表中
        if (supercls) {
            addSubclass(supercls, cls);
        } else {
            addRootClass(cls);
        }
    
        // 将类对象 cls 的分类附加到类对象 cls 上
        methodizeClass(cls);
    
        // 返回已实现的类对象 cls
        return cls;
    }
    
  • +initialize 方法的原理总结

    ① +initialize 方法的调用方式:

    系统自动调用 +initialize 方法的方式为,通过消息机制间接调用(objc_msgSend
    开发者手动调用 +initialize 方法的方式为,通过消息机制间接调用(objc_msgSend
    即,系统和开发者都是通过消息机制(objc_msgSend)间接地调用 +initialize 方法

    ② 系统自动调用 +initialize 方法的时刻:

    在第一次向类对象(或者元类对象)发送消息之前,会先调用类对象的 +initialize 方法
    也就是说,类对象的 +initialize 方法是以懒加载的方式被调用的

    ③ 在同一个镜像中,系统自动调用 +initialize 方法的顺序

    先调用父类对象的 +initialize 方法,再调用子类对象的 +initialize 方法(先初始化父类对象,再初始化子类对象)

    如果子类对象没有实现 +initialize 方法,则系统会去调用父类对象的 +initialize 方法
    因此,父类对象的 +initialize 方法可能会被调用多次。但是这并不代表父类对象会被初始化多次,每个类对象只会被初始化 1 次

    ④ 在同一个镜像中,开发者手动调用 +initialize 方法的顺序

    因为开发者手动调用 +initialize 方法的方式为通过消息机制间接调用(objc_msgSend),所以:

    1. 如果子类未实现 +initialize 方法,则会调用父类的 +initialize 方法
    2. 如果宿主类与分类同时实现了 +initialize 方法,则会调用分类的 +initialize 方法
    3. 如果一个宿主类的多个分类同时实现了 +initialize 方法,则会调用最后参与编译的分类的 +initialize 方法

练习:Objective-C 中 +initialize 方法的调用顺序

新建一个名为 InitializeMethodDemo 的 iOS - App,并创建以下 Class:
Person 继承自 NSObjectStudent 继承自 Person

+Initialize 方法的默认实现如下:

+(void)initialize {
    NSLog(@"%s", __func__);
}
  • ① 当 PersonStudent 都有 +Initialize 方法的默认实现时,进行如下方法调用:

    -(void)callInitializeMethod {
        [Student alloc];
        // +[Person initialize]
        // +[Student initialize]
    }
    
  • ② 当 PersonStudent 都有 +Initialize 方法的默认实现时,进行如下方法调用:

    -(void)callInitializeMethod {
        [Person alloc];
        // +[Person initialize]
        [Student alloc];
        // +[Student initialize]
    }
    
  • ③ 当 Person+Initialize 方法的默认实现,并且 Student 没有+Initialize 方法的默认实现时,进行如下方法调用:

    -(void)callInitializeMethod {
        [Student alloc];
        // +[Person initialize]
        // +[Person initialize]
    }
    
  • ④ 当 Person+Initialize 方法的默认实现,并且 Student 没有+Initialize 方法的默认实现时,进行如下方法调用:

    -(void)callInitializeMethod {
        [Student initialize];
        // +[Person initialize]
        // +[Person initialize]
        // +[Person initialize]
    }
    
  • ⑤ 为 Person 创建一个分类 Person(Male),当 PersonPerson(Male) 都有 +Initialize 方法的默认实现,并且 Student 没有+Initialize 方法的默认实现时,进行如下方法调用:

    -(void)callInitializeMethod {
        [Student alloc];
        // +[Person(Male) initialize]
        // +[Person(Male) initialize]
    }
    

Objective-C 中 +load 方法与 +initialize 方法的对比

Objective-C 中 +load 方法与 +initialize 方法的对比

Objective-C 中 +load 方法调用顺序大总结

  • 在不同镜像中

    ① dyld 会根据 App 主程序中对动态库的编译顺序来初始化动态库的镜像(先编译先初始化,后编译后初始化)
    ② dyld 会优先初始化动态库的镜像,然后再初始化 App 主程序的镜像(App 主程序的镜像最后初始化)
    ③ 在同一个镜像内,Objective-C 的 +load 方法 会比 C++ 的 __attribute__((constructor) 函数 先调用
    ④ 所有镜像(包括 App 主程序的镜像)中的 +load 方法__attribute__((constructor) 函数 都会比 主程序的 main 函数 先调用

  • 在相同镜像中

    当 dyld 初始化镜像时,会通过 RunTime 的 load_images 函数(准备和执行)镜像中所有 Class 和 Category 的 +load 方法。不管程序中有没有用到这些 Class 和 Category,只要镜像被初始化,这些 Class 和 Category 就都会被加载进内存并调用 +load 方法

    ⑤ 系统自动调用 +load 方法的方式为通过函数地址直接调用(IMP):

    1. 先调用所有类对象的 +load 方法,按照编译顺序调用(先编译先调用,后编译后调用)。并且在调用子类对象的 +load 方法之前会先调用父类对象的 +load 方法
    2. 再调用所有分类对象的 +load 方法,按照编译顺序调用(先编译先调用,后编译后调用)
      注意:分类对象与宿主类对象同名的普通方法是(后编译先调用)
      注意:分类对象 +load 方法的调用顺序只与分类对象的编译顺序有关,而与分类对象的继承关系无关
    3. 系统只会自动调用实现了 +load 方法的 Class 与 Category 中的 +load 方法
      系统不会自动调用没有实现 +load 方法的 Class 与 Category 中的 +load 方法
    4. 系统自动调用 +load 方法时,Class 与 Category 中的 +load 方法不会产生覆盖,都会被调用

    ⑥ 除非开发者手动调用,否则每个 Class 和 Category 的 +load 方法在程序运行期间只会被调用 1 次

    ⑦ 开发者手动调用 +load 方法的方式为通过消息机制间接调用(objc_msgSend):

    1. 如果子类未实现 +load 方法,则会调用父类的 +load 方法
    2. 如果宿主类与分类同时实现了 +load 方法,则会调用分类的 +load 方法
    3. 如果一个宿主类的多个分类同时实现了 +load 方法,则会调用最后参与编译的分类的 +load 方法
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值