目录
Objective-C 中 +load 方法的底层原理
-
源码分析: RunTime 向 dyld 注册 镜像初始化 的回调
在 MachO && dyld(五)中,我们介绍了 dyld 加载和链接动态库的流程,整个流程可细分为 9 步 :
① 设置运行环境
② 加载系统共享缓存
③ 实例化主程序
④ 加载插入的动态库
⑤ 链接主程序
⑥ 链接插入的动态库
⑦ 执行弱符号绑定
⑧ 执行初始化方法
⑨ 查找 App 入口点并返回这里重点关注:⑧ 执行初始化方法,在这一步中:
dyld
的ImageLoaderMachO::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
为 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 件事:- 调用
prepare_load_methods
函数准备+load
方法 - 调用
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
),所以:- 如果子类未实现
+load
方法,则会调用父类的+load
方法 - 如果宿主类与分类同时实现了
+load
方法,则会调用分类的+load
方法 - 如果一个宿主类的多个分类同时实现了
+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
函数的主要功能有:- 在当前类对象中进行不加锁的方法缓存查找
- 加锁 && 检查当前类对象是否已知
- 如果当前类对象没有实现(
isRealized
)或者初始化(isInitialized
),则实现或者初始化当前类对象 - 尝试在当前类对象的方法缓存以及方法列表中查找方法实现
- 尝试在父类对象的方法缓存以及方法列表中查找方法实现
- 如果没有找到方法实现,则尝试进行动态方法解析
- 如果动态方法解析失败,则尝试进行消息转发
- 解锁、返回方法实现
// 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_msgSend
到lookUpImpOrForward
的函数调用栈,属于 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
),所以:- 如果子类未实现
+initialize
方法,则会调用父类的+initialize
方法 - 如果宿主类与分类同时实现了
+initialize
方法,则会调用分类的+initialize
方法 - 如果一个宿主类的多个分类同时实现了
+initialize
方法,则会调用最后参与编译的分类的+initialize
方法
- 如果子类未实现
练习:Objective-C 中 +initialize 方法的调用顺序
新建一个名为 InitializeMethodDemo 的 iOS - App,并创建以下 Class:
Person
继承自 NSObject
,Student
继承自 Person
有 +Initialize
方法的默认实现如下:
+(void)initialize {
NSLog(@"%s", __func__);
}
-
① 当
Person
与Student
都有+Initialize
方法的默认实现时,进行如下方法调用:-(void)callInitializeMethod { [Student alloc]; // +[Person initialize] // +[Student initialize] }
-
② 当
Person
与Student
都有+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)
,当Person
与Person(Male)
都有+Initialize
方法的默认实现,并且Student
没有+Initialize
方法的默认实现时,进行如下方法调用:-(void)callInitializeMethod { [Student alloc]; // +[Person(Male) initialize] // +[Person(Male) 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
):- 先调用所有类对象的
+load
方法,按照编译顺序调用(先编译先调用,后编译后调用)。并且在调用子类对象的+load
方法之前会先调用父类对象的+load
方法 - 再调用所有分类对象的
+load
方法,按照编译顺序调用(先编译先调用,后编译后调用)
注意:分类对象与宿主类对象同名的普通方法是(后编译先调用)
注意:分类对象+load
方法的调用顺序只与分类对象的编译顺序有关,而与分类对象的继承关系无关 - 系统只会自动调用实现了
+load
方法的 Class 与 Category 中的+load
方法
系统不会自动调用没有实现+load
方法的 Class 与 Category 中的+load
方法 - 系统自动调用
+load
方法时,Class 与 Category 中的+load
方法不会产生覆盖,都会被调用
⑥ 除非开发者手动调用,否则每个 Class 和 Category 的
+load
方法在程序运行期间只会被调用 1 次⑦ 开发者手动调用
+load
方法的方式为通过消息机制间接调用(objc_msgSend
):- 如果子类未实现
+load
方法,则会调用父类的+load
方法 - 如果宿主类与分类同时实现了
+load
方法,则会调用分类的+load
方法 - 如果一个宿主类的多个分类同时实现了
+load
方法,则会调用最后参与编译的分类的+load
方法
- 先调用所有类对象的