根类NSObject
中有两个类方法用于加载和初始化类:
+ (void)load;
+ (void)initialize;
区别 | +load |
+initialize |
---|---|---|
调用时机 | 在类被加载之后,调用+initialize 之前调用 |
用于加载该类之后初次使用该类之前调用 |
实现环境 | 可以由类实现,也可以在分类实现 | 不能在分类重写 |
实现处代码 | 当类被加载时,该处实现代码被调用一次 | 当类收到第一条消息时,+initialize 会被调用一次;没有收到消息就不会被调用 |
1、分析objc-loadmethod.m
文件
关于+load
方法的大部分操作,Runtime 封装在文件objc-loadmethod.m
中。想要了解+load
方法的调用流程,首先需要了解一下关于+load
方法的功能函数!!
1.1、该文件中的变量
1.1.1、存储实现+load
方法类的数组
struct loadable_class {
Class cls;
IMP method;
};
static struct loadable_class *loadable_classes = nil;
static int loadable_classes_used = 0;
static int loadable_classes_allocated = 0;
- 结构体
loadable_class
用于存储实现了+load
方法的Class
和+load
方法的实现IMP
; - 结构数组
loadable_classes
:存储结构loadable_class
; - 静态变量
loadable_classes_used
用于记录add_class_to_loadable_list()
函数的调用次数,同时也是loadable_classes
数组的元素个数;
1.1.2、存储实现+load
方法分类的数组
//存储了 +load 方法所属的 Category 和 +load 方法的IMP
struct loadable_category {
Category cat; //+load 方法所属的 Category;可能为 nil
IMP method;
};
static struct loadable_category *loadable_categories = nil;
static int loadable_categories_used = 0;
static int loadable_categories_allocated = 0;
- 结构体
loadable_category
用于存储实现了+load
方法的category
和+load
方法的实现IMP
; - 结构数组
loadable_categories
:存储元素为loadable_category
; - 静态变量
loadable_categories_used
用于记录add_category_to_loadable_list()
函数的调用次数,也是loadable_categories
数组的元素个数;
1.2、结构数组 loadable_classes
的增删操作
1.2.1、向数组 loadable_classes
添加一个类
void add_class_to_loadable_list(Class cls){
IMP method;
loadMethodLock.assertLocked();
method = cls->getLoadMethod();//获取类 cls 的 +load 方法的 IMP
if (!method) return;
if (PrintLoading) {
_objc_inform("LOAD: class '%s' scheduled for +load", cls->nameForLogging());
}
if (loadable_classes_used == loadable_classes_allocated) {
//如果数组 loadable_classes 分配的内存已用完,对数组进行动态扩容
loadable_classes_allocated = loadable_classes_allocated*2 + 16;
loadable_classes = (struct loadable_class *)realloc(loadable_classes,
loadable_classes_allocated *sizeof(struct loadable_class));
}
loadable_classes[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
loadable_classes_used++;
}
该函数的主要的功能就是将一个实现了 +load
的指定类添加到数组 loadable_classes
中:
- 如果该类没有实现
+load
方法,自然是什么操作也不做; - 如果如果数组
loadable_classes
分配的内存已用完,对数组进行动态扩容; - 将指定的类与
IMP
存储在数组loadable_classes
中; -
loadable_classes_used
数组加 1,用于记录该函数的调用次数;相当于数组loadable_classes
的元素个数;
1.2.2、移除数组 loadable_classes
中某个类
void remove_class_from_loadable_list(Class cls){
loadMethodLock.assertLocked();
if (loadable_classes) {
int i;
//遍历结构数组 loadable_classes,根据入参 cls 找到数组中指定的元素
for (i = 0; i < loadable_classes_used; i++) {
if (loadable_classes[i].cls == cls) {
loadable_classes[i].cls = nil;
if (PrintLoading) {
_objc_inform("LOAD: class '%s' unscheduled for +load", cls->nameForLogging());
}
return;
}
}
}
}
该函数的主要功能就是遍历数组loadable_classes
,找到指定的类,并将该类从数组loadable_classes
中移除。
1.3、结构数组 loadable_categories
的增删操作
1.3.1、向数组 loadable_categories
添加一个分类
void add_category_to_loadable_list(Category cat){
IMP method;
loadMethodLock.assertLocked();
method = _category_getLoadMethod(cat);
if (!method) return;
if (PrintLoading) {
_objc_inform("LOAD: category '%s(%s)' scheduled for +load", _category_getClassName(cat), _category_getName(cat));
}
if (loadable_categories_used == loadable_categories_allocated) {
//如果数组 loadable_categories 分配的内存已用完,对数组进行动态扩容
loadable_categories_allocated = loadable_categories_allocated*2 + 16;
loadable_categories = (struct loadable_category *)
realloc(loadable_categories,
loadable_categories_allocated *
sizeof(struct loadable_category));
}
loadable_categories[loadable_categories_used].cat = cat;
loadable_categories[loadable_categories_used].method = method;
loadable_categories_used++;
}
该函数的主要的功能就是将一个实现了 +load
的指定分类添加到数组 loadable_categories
中:
- 如果该分类没有实现
+load
方法,自然是什么操作也不做; - 如果数组
loadable_categories
分配的内存已用完,对数组进行动态扩容; - 将指定的分类与
IMP
存储在数组loadable_categories
中; -
loadable_categories_used
数组加 1,用于记录该函数的调用次数;相当于数组loadable_categories
的元素个数;
1.3.2、移除数组 loadable_categories
中某个分类
void remove_category_from_loadable_list(Category cat){
loadMethodLock.assertLocked();
if (loadable_categories) {
int i;
//遍历结构数组 loadable_categories,根据入参 cat 找到数组中指定的元素
for (i = 0; i < loadable_categories_used; i++) {
if (loadable_categories[i].cat == cat) {
loadable_categories[i].cat = nil;
if (PrintLoading) {
_objc_inform("LOAD: category '%s(%s)' unscheduled for +load",_category_getClassName(cat), _category_getName(cat));
}
return;
}
}
}
}
该函数的主要功能就是遍历数组 loadable_categories
,找到指定的分类,并将该分类从数组 loadable_categories
中移除。
1.4、使用数组 loadable_categories
与 loadable_classes
在objc-loadmethod.m
文件的 call_load_methods()
函数,是调用程序中实现的 +load
方法的入口:
void call_load_methods(void){
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
// 1. 重复调用 loadable_classes 数组上的 +load,直到不再有其他类为止
while (loadable_classes_used > 0) {
//遍历数组 loadable_classes ,调用数组中所有挂起的类的 +load 方法; 调用之后,将 loadable_classes_used 置为 0;
call_class_loads();
}
// 2. 调用 loadable_categories 数组中的 +load 一次:调用该函数期间,是否有新的分类添加到数组
more_categories = call_category_loads();
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
该函数的主要功能就是:
- 调用
call_class_loads()
函数,执行数组loadable_classes
中的+load
方法; - 调用
call_category_loads()
函数,执行数组loadable_categories
中的+load
方法;
1.4.1、调用数组loadable_classes
中的 +load
方法
static void call_class_loads(void){
int i;
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
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; //如果该类为 nil ,则直接返回
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
(*load_method)(cls, SEL_load);//通过函数指针执行指定类 cls 的 +load 方法
}
if (classes) free(classes);//释放列表
}
该函数的主要功能就是:
- 遍历数组
loadable_classes_used
,取出每个元素的类与+load
的实现,然后执行该实现(*load_method)(cls, SEL_load)
; - 遍历数组
loadable_classes_used
完毕,释放该数组,重置计数loadable_classes_used
;
1.4.2、调用数组loadable_categories
中的 +load
方法
static bool call_category_loads(void){
int i, shift;
bool new_categories_added = NO;//该函数返回值
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;
for (i = 0; i < used; i++) {
Category cat = cats[i].cat;
load_method_t load_method = (load_method_t)cats[i].method;//获取分类 +load 方法的 IMP
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, SEL_load);//通过函数指针执行指定分类的 +load 方法
cats[i].cat = nil;
}
}
//将所有调用 +load 方法的分类移除数组
shift = 0;
for (i = 0; i < used; i++) {
if (cats[i].cat) {
cats[i-shift] = cats[i];
} else {
shift++;
}
}
used -= shift;
//将新添加到数组loadable_categories的分类存储到数组 cats 上
new_categories_added = (loadable_categories_used > 0);//是否有新的分类添加到数组 loadable_categories
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];
}
if (loadable_categories) free(loadable_categories);//释放数组 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;
}
if (PrintLoading) {
if (loadable_categories_used != 0) {
_objc_inform("LOAD: %d categories still waiting for +load\n",loadable_categories_used);
}
}
return new_categories_added;
}
该函数的主要功能就是:
- 遍历数组
loadable_categories
,取出每个元素的分类与+load
的实现,然后执行该实现(*load_method)(cls, SEL_load)
; - 将所有调用
+load
方法的分类移除数loadable_classes_used
; - 接着判断是否有新的分类被添加到数组
loadable_categories
;如果有,则返回YES
1.5、文件objc-loadmethod.m
功能总结
1.5.1、全局变量
- 结构数组
loadable_classes
中的每个元素都存储了类以及它的+load
方法的IMP
; - 结构数组
loadable_categories
中的每个元素都存储了分类以及它的 +load 方法的 IMP;
1.5.2、功能函数
-
add_class_to_loadable_list()
函数将一个实现了+load
方法的类添加到数组loadable_classes
; -
add_category_to_loadable_list()
函数将一个实现了+load
方法的分类添加到数组loadable_categories
; -
remove_class_from_loadable_list()
函数从数组loadable_classes
中移除指定类; -
remove_category_from_loadable_list()
函数从数组loadable_categories
中移除指定分类; -
call_class_loads()
函数遍历数组loadable_classes
调用数组中所有挂起的类的+load
方法;调用之后数组loadable_classes
置为nil
; -
call_category_loads()
函数遍历数组loadable_categories
调用分类挂起的+load
方法; -
call_load_methods()
函数调用所有挂起(未注册)的类和分类的+load
方法。
2、+load
方法的调用历程
从 objc-loadmethod.m
文件中的变量与函数来看:程序中类或者分类实现的+load
方法,Runtime 都记录在了数组loadable_classes
与数组 loadable_categories
,那么何时调用指定类或者分类的+load
方法呢? 也就是说何时使用这两个数组的元素呢?
首先打印PeopleModel
类的+load
方法的调用栈信息:
(lldb) thread backtrace
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
* frame #0: 0x0000000100000bd7 objc-test`+[PeopleModel load](self=PeopleModel, _cmd="load") at PeopleModel.m:12
frame #1: 0x000000010037218c libobjc.A.dylib`call_class_loads() at objc-loadmethod.mm:200
frame #2: 0x0000000100359b0d libobjc.A.dylib`::call_load_methods() at objc-loadmethod.mm:326
frame #3: 0x000000010035996a libobjc.A.dylib`::load_images(path="/Users/longlong/Library/Developer/Xcode/DerivedData/SourceCode-gbviaxaumpwhkieshjwwgrxrxpva/Build/Products/Debug/objc-test", mh=0x0000000100000000) at objc-runtime-new.mm:2035
frame #4: 0x000000010000a454 dyld`dyld::notifySingle(dyld_image_states, ImageLoader const*, ImageLoader::InitializerTimingList*) + 420
frame #5: 0x000000010001c0c5 dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 337
frame #6: 0x000000010001b254 dyld`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 134
frame #7: 0x000000010001b2e8 dyld`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 74
frame #8: 0x000000010000a774 dyld`dyld::initializeMainExecutable() + 199
frame #9: 0x000000010000f78f dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 6237
frame #10: 0x00000001000094f6 dyld`dyldbootstrap::start(macho_header const*, int, char const**, long, macho_header const*, unsigned long*) + 1154
frame #11: 0x0000000100009036 dyld`_dyld_start + 54
为了优化程序启动速度和利用动态库缓存,iOS 系统采用了共享缓存技术;dyld 缓存在 iOS系统中。
dyld 从 dyldStartup.s
文件开始执行:
- 其中用汇编实现的
__dyld_start
函数里面调用了dyldbootstrap::start()
函数; - 然后调用了
dyld::_main()
函数,创建镜像加载类ImageLoader
,分析 Mach-O 文件; - 接着根据指定的 Mach-O 文件的头信息
mach_header
调用load_images()
函数加载镜像信息
针对 dyld 的调用流程,这里不做过多分析!笔者从 Runtime 库中objc-runtime-new.mm
文件的load_images()
函数说起:
void load_images(const char *path __unused, const struct mach_header *mh){
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
call_load_methods();
}
通过该函数的入参可知,该函数可能会被多次调用,每次调用,处理一个指定 mach_header
中的 +load
信息。该函数的全部代码如上所示,执行的功能很简单:
- 1、判断该 Mach-O 文件是否有实现的
+load
方法,如果没有就直接返回;否则向下执行; - 2、调用
prepare_load_methods()
函数将该 Mach-O 文件中所有实现的+load
方法分别存储到数组loadable_classes
与数组loadable_categories
; - 3、调用
call_load_methods()
函数执行数组loadable_classes
与数组loadable_categories
的+load
方法;
关于call_load_methods()
函数的功能,笔者在章节 1已经详细讲过,这里不再重复!我们来看看 prepare_load_methods()
函数:
2.1、prepare_load_methods()
函数
void prepare_load_methods(const headerType *mhdr){
size_t count, i;
runtimeLock.assertLocked();
//获取实现了+load方法的类的列表
classref_t *classlist = _getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
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());
add_category_to_loadable_list(cat);
}
}
分析该函数,主要功能为:
- 1、获取程序中所有实现了
+load
方法的类列表classlist
与分类列表categorylist
; - 2、遍历类列表
classlist
,通过调用schedule_class_load()
函数,将类与它的+load
方法存储到数组loadable_classes
中; - 3、遍历分类列表
categorylist
,通过调用add_category_to_loadable_list()
函数,将分类与它的+load
方法存储到数组loadable_categories
中;
2.1.1、schedule_class_load()
函数
static void schedule_class_load(Class cls){
if (!cls) return;
assert(cls->isRealized()); // _read_images should realize
// 已经添加 Class 的 +load 方法到调用列表中
if (cls->data()->flags & RW_LOADED) return;
// 确保 super 已经被添加到 +load 列表中,默认是整个继承者链的顺序
schedule_class_load(cls->superclass);
// 将IMP和Class添加到调用列表
add_class_to_loadable_list(cls);
// 设置Class的flags,表示已经添加Class到调用列表中
cls->setInfo(RW_LOADED);
}
在该函数中,看到了 add_class_to_loadable_list()
函数将类与它的+load
方法存储到数组loadable_classes
中。
在存入数组loadable_classes
之前,必须确保它的父类如果实现+load
方法则在它之前加入数组loadable_classes
。
2.2、调用时机
众所周知,程序通过入口函数 main()
开始执行,而 +load
的执行,却是在 Runtime 中实现的,此时程序并没有走到入口函数 main()
!
+load
方法可能被加载多次嘛?
如何确保+load
方法只被加载一次?
总结:
-
+load
方法是 Runtime 调用的,无需开发者去调用; -
+load
的调用顺序遵循:父类调用+load
之后,子类才能调用; -
+load
方法是在所有类被 Runtime 写入内存后才调用的; - 子类的
+load
方法是按照 Compile Sources 的排列顺序加载的,但要遵循调用子类加载之前,必须先调用其父类的+load
方法。 - 在所有类的
+load
方法调用完以后再调用分类的+load
方法,分类的+load
方法调用顺序完全按照 Compile Sources 排列顺序。 -
+load
方法还有一个非常重要的特性,那就是子类、父类和分类中的+load方法的实现是被区别对待的。 Runtime 自动调用+load
方法时,分类中的+load
方法并不会对主类中的+load
方法造成覆盖。