【iOS重学】一篇文章讲清楚+load和+initialize

写在前面

本文主要从底层源码上来分析一下+load+initialize方法的调用顺序以及它们之间的区别。

+load

+load方法会在Runtime加载类、分类的时候调用,每个类、分类的+load方法在程序运行过程中只会调用一次。

+load的基本使用

// Person 类
@interface Person : NSObject

@end

@implementation Person

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

@end
  
// Person + Test1 类
@interface Person (Test1)

@end 

@implementation Person (Test1)

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

@end

// Person + Test2 类
@interface Person (Test2)

@end
  
@implementation Person (Test2)

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

@end

// Student 类 继承自Person
@interface Student : Person

@end
  
@implementation Student

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

@end

// Student + Test1 类
@interface Student (Test1)

@end
  
@implementation Student (Test1)

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

@end

// Student + Test2 类
@interface Student (Test2)

@end

@implementation Student (Test2)

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

@end

上面场景,+load方法的打印顺序为:

2022-11-28 09:50:34.392232+0800 CategoryDemo[88835:2251283] +[Person load]
2022-11-28 09:50:34.392867+0800 CategoryDemo[88835:2251283] +[Student load]
2022-11-28 09:50:34.392964+0800 CategoryDemo[88835:2251283] +[Animal load]
2022-11-28 09:50:34.393013+0800 CategoryDemo[88835:2251283] +[Student(Test1) load]
2022-11-28 09:50:34.393056+0800 CategoryDemo[88835:2251283] +[Person(Test1) load]
2022-11-28 09:50:34.393107+0800 CategoryDemo[88835:2251283] +[Student(Test2) load]
2022-11-28 09:50:34.393151+0800 CategoryDemo[88835:2251283] +[Person(Test2) load]

那么,它们之间究竟是什么样的一个加载顺序呢?

+load的底层源码

+load方法的源码查看顺序:

// objc-os文件
1. _objc_init
2. load_images

// objc-runtime-new文件
3. prepare_load_methods
  3.1 schedule_class_load
  3.2 add_category_to_loadable_list
4. call_load_methods
  4.1 call_class_loads - (*load_method)(cls, @selector(load))
  4.2 call_category_loads

【iOS重学】Category的底层原理中博主提到Runtime入口就是:objc-os文件中的_objc_init方法,我们就从这里入手分析一下底层源码。

1.png

解释
1、先按照编译顺序将所有的类add_class_to_loadable_list装载到loadable_classes的数组中。
2、再按照编译顺序将所有的分类add_class_to_loadable_list装载到loadable_classes的数组中。

schedule_class_load方法源码如下:

2.png

解释
在装载类到loadable_classes数组中时,如果存在父类,先将父类装载到loadable_classes中,再将类加载到数组中。

call_load_methods方法源码如下:

3.png

解释
在调用+load方法时,先调用类的+load方法再调用分类的+load方法。

+load的调用顺序总结

1、先调用类的+load
1.1 按照编译顺序进行调用(先编译 -> 先调用)
1.2 调用子类+load之前会先调用父类的+load

2、再调用分类的+load
2.1 按照编译顺序进行调用(先编译 -> 先调用)

注意+load只会调用一次,比如StudentPerson之前编译,会先调用Person+load方法,表示Person已经被装载进内存了,所以+load不会被调用多次。

+load的调用方式

4.png
struct loadable_class {
    Class cls;  // may be nil
    IMP method; // 这个method 就是+load的IMP 这个loadable_class就是用来加载类的结构体
};

如上图call_class_loads方法所示,+load方法的调用方式是:直接根据+load方法的函数地址直接去调用。

+initialize

+initialize方法会在类第一次接收到消息的时候调用。

+initialize的基本使用

// Person 类
@interface Person : NSObject

@end

@implementation Person

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

@end
  
// Person + Test1 类
@interface Person (Test1)

@end 

@implementation Person (Test1)

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

@end

// Person + Test2 类
@interface Person (Test2)

@end
  
@implementation Person (Test2)

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

@end

// Student 类 继承自Person
@interface Student : Person

@end
  
@implementation Student

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

@end

// Student + Test1 类
@interface Student (Test1)

@end
  
@implementation Student (Test1)

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

@end

// Student + Test2 类
@interface Student (Test2)

@end

@implementation Student (Test2)

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

@end

上面场景,+initialize方法的打印顺序为:

2022-11-28 18:00:10.526685+0800 CategoryDemo[57742:2613672] +[Person(Test2) initialize]
2022-11-28 18:00:10.527249+0800 CategoryDemo[57742:2613672] +[Student(Test2) initialize]

+initialize的底层源码

+initialize方法源码的查看顺序:

// objc-runtime-new.mm文件
1. class_getInstanceMethod
2. lookUpImpOrForward
3. realizeAndInitializeIfNeeded_locked
4. initializeAndLeaveLocked
5. initializeAndMaybeRelock
6. initializeNonMetaClass
7. callInitialize

initializeNonMetaClass方法源码如下:

5.png

6.png

解释
从上面的源码我们大概可以看到:在调用callInitialize方法之前会去检查是否存在父类和父类是否被初始化,会先去调用父类的+initialize方法。

+initialize的调用顺序总结

先调用父类的+initialize,再调用子类的+initialize
注意
1、先初始化父类再初始化子类,每个类只会被初始化一次,但是可能会被调用多次。
比如下面场景:
1.Student没有实现+initialize方法,调用[Person alloc] [Student alloc]会调用两次Person+initialize方法。
打印结果如下:

2022-11-28 18:01:47.579047+0800 CategoryDemo[57804:2615728] +[Person(Test2) initialize]
2022-11-28 18:01:47.579702+0800 CategoryDemo[57804:2615728] +[Person(Test2) initialize]

2.Student实现了+initialize方法,Person调用过了+initialize,那么就不会再调用了。

+initialize调用方式

void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
    asm("");
}

如上图callInitialize方法所示,+initialize方法的调用方式是:obj_msgSend

+load和+initialize对比

1、调用时机:
+load是在Runtime加载类、分类的时候调用(只会调用一次),在main函数之前。
+initialize是在类第一次接收到消息的时候调用,只会初始化一次(父类的+initialize可能会被调用多次),在main函数之后。

2、调用方式:
+load是根据函数地址直接调用。
+initialize是通过objc_msgSend调用。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,651评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,468评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,931评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,218评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,234评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,198评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,084评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,926评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,341评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,563评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,731评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,430评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,036评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,676评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,829评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,743评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,629评论 2 354

推荐阅读更多精彩内容