底层初窥——load及initialize

load 方法

  • 每当类或类别被添加到 Objective-C 运行时时调用;
  • 实现此方法以在加载时执行类特定的行为。

开发文档解读

load 开发文档

Discussion

当类和分类被动态加载和静态链接时,会发送 load 消息给对应的类和分类,但只有新加载的类或分类才会响应这个方法。

初始化顺序如下:

  1. 链接到的任何框架中的所有初始化程序。
  2. 模块中的所有 +load 方法。
  3. 模块中的所有C ++静态初始化程序和C / C ++ attribute(constructor)函数。
  4. 链接到您的框架中的所有初始化程序。

另外:

  • 子类的 +load 方法会在其父类之后调用。
  • 分类的 +load 方法都会其本类之后调用。

Important

桥接到 Objective-C 的 Swift 类的自定义 +load 方法不会自动调用。

源码解读

通过文档,我们知道:+load 方法是在类或其分类被添加到运行时调用。

_objc_init –> load_images –> prepare_load_images –> schedule_class_load –> call_load_methods –> (call_class_loads & class_category_loads)

在 runtime 的源码中,我们先找到 runtime 的初始化方法,在其初始化方法中调用了 load_image 方法,我们可以在其中找到跟 +load 方法有关的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void
load_images(const char *path __unused, const struct mach_header *mh)
{
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;

recursive_mutex_locker_t lock(loadMethodLock);

// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}

// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}

这里我们看到很明显的两个跟 +load 方法有关的方法名:

  • prepare_load_methods((const headerType *)mh);
  • call_load_methods();

prepare_load_methods((const headerType *)mh);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;

runtimeLock.assertLocked();

// 获取所有需要发送 load 消息的 classlist
classref_t *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
// 处理 class
schedule_class_load(remapClass(classlist[i]));
}

// 获取所有需要发送 load 消息的 categorylist
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);
}
}

// 该方法通过递归调度以先父类再子类的顺序调用 add_class_to_loadable_list 方法
static void schedule_class_load(Class cls)
{
if (!cls) return;
assert(cls->isRealized()); // _read_images should realize

if (cls->data()->flags & RW_LOADED) return;

// Ensure superclass-first ordering
schedule_class_load(cls->superclass);

add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}

// 将按照父类 -> 子类的顺序,将类加入到 loadable_classes 中
void add_class_to_loadable_list(Class cls)
{
IMP method;

loadMethodLock.assertLocked();

method = cls->getLoadMethod();
if (!method) return; // Don't bother if cls has no +load method

if (PrintLoading) {
_objc_inform("LOAD: class '%s' scheduled for +load",
cls->nameForLogging());
}

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));
}

// 待调用 +load 方法的类数组
loadable_classes[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
loadable_classes_used++;
}

// 将分类加入到 loadable_categories 中
void add_category_to_loadable_list(Category cat)
{
IMP method;

loadMethodLock.assertLocked();

method = _category_getLoadMethod(cat);

// Don't bother if cat has no +load method
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_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++;
}

在 prepare_load_methods 方法中,做了两件事:

  • 将需要调用 +load 方法的类按照 父类 -> 子类的顺序将它们分别加入到 loadable_classes 中。
  • 将需要调用 +load 方法的分类添加到 loadable_categories 中。

call_load_methods();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;

loadMethodLock.assertLocked();

// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;

// 创建了自动释放池
void *pool = objc_autoreleasePoolPush();

do {
// 循环调用 class +load 方法,直到 loadable_classes 空
while (loadable_classes_used > 0) {
call_class_loads();
}

// 2. 调用一次 category 的 +load 方法
more_categories = call_category_loads();

// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);

objc_autoreleasePoolPop(pool);

loading = NO;
}

// 类的 +load 方法入口函数
static void call_class_loads(void)
{
int i;

// 这里获取的 classes 就是在 prepare_load_methods 方法中处理好的所有需要调用 +load 方法的类
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
// 指向 用于保存类信息的内存的首地址
loadable_classes = nil;
// 标识已分配的内存空间大小
loadable_classes_allocated = 0;
//标识已使用的内存空间大小
loadable_classes_used = 0;

// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;

if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
(*load_method)(cls, SEL_load);
}

// Destroy the detached list.
if (classes) free(classes);
}

// 分类的 +load 方法入口函数
static bool call_category_loads(void)
{...

代码量较大这里就不贴了
...}

call_load_methods 中可以得到下面几点结论:

  • 从前往后取出 loadable_classes 数组中的类,并调用其 +load 方法。
  • 当类的 +load 方法调用完毕之后,调用分类的 +load 方法。
  • 调用类和分类的 +load 方法是直接使用函数内存地址的方式 ==(*load_method)(cls, SEL_load);== ,而不是使用发送消息 objc_msgSend 的方式。因此 load 方法无继承关系,比如子类没有实现 +load 方法,加载子类的时候是不会调用父类的 +load 方法的;同理一个类和其分类的 +load 方法都会被调用。

加载顺序

通过上面的源码解读,我们可以得出 +load 方法的加载顺序如下:

  • +load 的执行顺序是先类,后 category。
  • 类中父类先于子类调用。

下面我们搞个 Demo 验证一下上面两点结论,并且看看 category 间的加载顺序。

category 间的加载顺序

我们先创建一个测试 Demo,项目如下:

Build Phases –> Compile Sources 中顺序:

在 Xcode 中点击 Edit Scheme,添加两个环境变量并将 Value 置为 YES:(可以在执行load方法以及加载category的时候打印log信息):

  • OBJC_PRINT_LOAD_METHODS:YES
  • OBJC_PRINT_REPLACED_METHODS:YES

打印信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
objc[31269]: LOAD: class 'Person' scheduled for +load
objc[31269]: LOAD: class 'Teacher' scheduled for +load
objc[31269]: LOAD: class 'Animal' scheduled for +load

objc[31269]: LOAD: category 'Teacher(CategoryForTeacher)' scheduled for +load
objc[31269]: LOAD: category 'Person(Category_One)' scheduled for +load
objc[31269]: LOAD: category 'Person(Category_Two)' scheduled for +load

objc[31269]: LOAD: +[Person load]
objc[31269]: LOAD: +[Teacher load]
objc[31269]: LOAD: +[Animal load]

objc[31269]: LOAD: +[Teacher(CategoryForTeacher) load]
objc[31269]: LOAD: +[Person(Category_One) load]
objc[31269]: LOAD: +[Person(Category_Two) load]

我们来更换一下 Build Phases –> Compile Sources 中顺序:

打印信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
objc[31286]: LOAD: class 'Animal' scheduled for +load
objc[31286]: LOAD: class 'Person' scheduled for +load
objc[31286]: LOAD: class 'Teacher' scheduled for +load

objc[31286]: LOAD: category 'Person(Category_One)' scheduled for +load
objc[31286]: LOAD: category 'Teacher(CategoryForTeacher)' scheduled for +load
objc[31286]: LOAD: category 'Person(Category_Two)' scheduled for +load

objc[31286]: LOAD: +[Animal load]
objc[31286]: LOAD: +[Person load]
objc[31286]: LOAD: +[Teacher load]

objc[31286]: LOAD: +[Person(Category_One) load]
objc[31286]: LOAD: +[Teacher(CategoryForTeacher) load]
objc[31286]: LOAD: +[Person(Category_Two) load]

从上面的打印我们可以得出 +load 方法的调用顺序:

  • 类会优先于分类调用。
  • 父类会优先于其子类调用。
  • 同一个类的多个分类加载顺序可查看 Target -> Build Phases -> Compile Sources。
  • 不同类的加载顺序可查看 Target -> Build Phases -> Compile Sources。

initialize 方法

在类接收到第一个消息之前执行 initialize 方法。

开发文档解读

initialize 方法文档

Discussion

runtime 会在每个类及其子类收到第一条消息(即调用方法)之前调用 initialize 方法。父类在其子类之前收到此消息。

runtime 以线程安全的方式将 initialize 消息发送给类。也就是说,initialize 由安全线程运行以将消息发送到一个类,此时其它任何线程调用该类的方法时,都会堵塞直至 initialize 方法完成。

如果子类未实现 initialize 方法,则其父类的实现会被多次调用(运行时将调用被继承类的实现),或者子类显式调用[super initialize]。如果要避免 initialize 多次调用,可以按照以下方式来构造实现:

1
2
3
4
5
+ (void)initialize {
if (self == [ClassName self]) {
// ... do the initialization ...
}
}

再次强调 initialize 方法会在安全线程内调用,尽量不要在该方法中写复杂逻辑,否则容易引起死锁。

Special Considerations

initialize 方法每个类只会被调用一次,如果想对某个类或其分类做独立初始化,请使用 +load 方法。

源码解读

根据文档解读,我们知道:initialize 方法在第一次接收到消息之前调用。

runtime objc_msgSend -> 消息发送阶段 _class_lookupMethodAndLoadCache3 –> lookUpImpOrForward –> _class_initialize

我们通过 runtime 的执行路径,可以找到 _class_initialize 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
当我们给某个类发送消息的时候,runtime会调用这个函数在类中查找相应方法的实现或转发。
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
// 如果类没有初始化,runtime 会调用 void _class_initialize(Class cls) 函数对该类进行初始化
if (initialize && !cls->isInitialized()) {
runtimeLock.unlock();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.lock();
// If sel == initialize, _class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}
}
  • _class_initialize
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
void _class_initialize(Class cls)
{
assert(!cls->isMetaClass());

Class supercls;
bool reallyInitialize = NO;

// 通过递归调用保证父类先于子类调用
supercls = cls->superclass;
if (supercls && !supercls->isInitialized()) {
_class_initialize(supercls);
}

// Try to atomically set CLS_INITIALIZING.
{
monitor_locker_t lock(classInitLock);
if (!cls->isInitialized() && !cls->isInitializing()) {
// 设置 cls 状态为 Initializing
cls->setInitializing();
// 设置 flag 值,保证下面进入初始化处理逻辑
reallyInitialize = YES;
}
}

// 需要进行初始化
if (reallyInitialize) {
// We successfully set the CLS_INITIALIZING bit. Initialize the class.

// Record that we're initializing this class so we can message it.
// 将 initialize 的方法调用放在安全县城内执行
_setThisThreadIsInitializingClass(cls);

if (MultithreadedForkChild) {
// 不会在被 fork() 之后调用 +initialize 方法
performForkChildInitialize(cls, supercls);
return;
}

// 这里省略了一些 tay-catch 及打印逻辑

// 真正调用 cls 的 +initialize 方法,点击进去查看其实就是
// ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
callInitialize(cls);

// 结束 initialize
lockAndFinishInitializing(cls, supercls);

return;
}
// 如果当前 cls 正在 initialize 中
else if (cls->isInitializing()) {
// 初始化已设置好,无需做任何操作
// 如果这个线程在前面设置了它,则继续正常运行
if (_thisThreadIsInitializingClass(cls)) {
return;
}
// 如果其它线程设置,则阻塞,直至初始化完成
else if (!MultithreadedForkChild) {
waitForInitializeToComplete(cls);
return;
} else {
// 我们位于fork()的子线程,面对的是一个在调用fork()时由其他线程初始化的类。
_setThisThreadIsInitializingClass(cls);
performForkChildInitialize(cls, supercls);
}
}

// 如果当前 cls initialize 结束
else if (cls->isInitialized()) {
return;
}

else {
// We shouldn't be here.
_objc_fatal("thread-safe class init in objc runtime is buggy!");
}
}

_class_initialize 中可以得到下面几点结论:

  • 说明该类接收到第一条消息之前才会调用 +initialize 方法。
  • 父类优先于子类调用。
  • 调用 +initialize 方法是通过 objc_msgSend 的方式。因此 +initialize 方法是有继承关系,比如子类没有实现 +initialize 方法,加载子类的时候会先调用父类的 +initialize 方法的。

拓展一下

Category

在我的另一篇文章初窥 Category中,我们知道,在 runtime 初始化时加载 Category 的。现在结合本文所讲,我们再来看一下 runtime 的初始化方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// objc-os.mm 870
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;

// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
lock_init();
exception_init();

_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
  • map_images:Category 在这个方法内加载。
  • load_images:+load 方法调用在这个方法内部处理。

所以,我们可以得出的结论:

  • Category 在 +load 方法执行前被加载。

那么,这也说明了:

  • 分类中的 +initialize 方法是会覆盖原类的。

特殊情况

在A类的 load 方法中调用了B类的类方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#import "Person.h"
@implementation Person
+ (void)load {
[Animal class];
NSLog(@"%s---", __FUNCTION__);
}

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

@implementation Animal

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

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

@end

----- 输出结果 -----
+[Animal initialize]
+[Person load]---
+[Animal load]---

如果在A类的 load 方法中调用 B 类的类方法,那么在调用 A 的 Load 方法之前,会先调用一下 B 类的 initialize 方法,但是 B 类的 load 方法还是按照 Compile Source 顺序进行加载。

总结

  • load 方法在类或类别被添加到 Objective-C runtime 时调用。
  • load 方法不同类的加载顺序按照 Compile Sources 顺序加载。
  • 类或分类的 load 方法都会被调用,且只会调用一次。调用顺序为:父类 -> 子类 -> 分类。多个分类会按照 Compile Sources 顺序加载。
  • initialize 可能会被调用多次,也可能被覆盖。
    • 子类父类都实现,先调动父类再调用子类。
    • 子类未实现时,会调用父类的。
    • 分类若实现了方法,会覆盖原类的。