1
2
+ (void)load;
+ (void)initialize;

Load

如果你实现了 + load 方法,那么当类被加载到内存时,它会自动被调用,并且是在 main 函数调用之前被调用。

load 方法的调用栈

首先分析一下 load 方法是如何被调用的

image.png

从控制台的打印可以看出,运行之后,依然打印了 XXXX load 字符串,也就是说调用了 + load 方法。

执行顺序

load 方法的调用顺序其实有迹可循,我们看到 demo 的项目设置如下:

image.png

TARGETS -> Build Phases -> Compile Sources中调整一下文件的加载顺序:

image.png

结果发现:

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

调用顺序

1、先调用类的 +load 方法,类先于分类调用

  • 按照编译先后顺序调用(先编译,先调用),文件的加载顺序靠前的类优先调用
  • 调用子类的 +load 方法之前会先调用父类的 +load 方法,即父类先于子类调用
    2、再调用分类的 +load 方法;
  • 按照编译先后顺序调用(先编译,先调用),文件的加载顺序靠前的分类优先调用

load方法如何加载到内存中

使用 Xcode 添加一个符号断点 +[Parent load]

image.png

注意:
注意这里 +[ 之间没有空格

重新运行程序。这时,代码会停在 NSLog(@"%@ , %s", [self class], __FUNCTION__); 这一行的实现上:

左侧的调用栈很清楚的展示哪些方法被调用了:

1
2
3
4
5
6
0  +[Parent load]
1 call_load_methods
2 load_images
3 dyld::notifySingle(dyld_image_states, ImageLoader const*, ImageLoader::InitializerTimingList*)
...
13 _dyld_start

runtime 初始化开始

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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();
runtime_init();
exception_init();
#if __OBJC2__
cache_t::init();
#endif
_imp_implementationWithBlock_init();

_dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}

首先类的加载会先进行 _objc_init 函数的调用,最后来到 _dyld_objc_notify_register 函数,传入 load_images

  • map_images 主要是在 image 加载进内容后对其二进制内容进行解析,初始化里面的类的结构等。
  • load_images 主要是调用 call_load_methods。按照继承层次依次调用 Class+load 方法然后再是 Category+load 方法。
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
void
load_images(const char *path __unused, const struct mach_header *mh)
{
// didInitialAttachCategories 标记加载分类的,默认值为 false,
// didCallDyldNotifyRegister 标记 _dyld_objc_notify_register 是否调用完成,
// 此时为 false,所以暂时此 if 内部不会执行。
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
loadAllCategories();
}

// Return without taking locks if there are no +load methods here.
// 如果 mh 中不包含 +load 就直接不加锁 return(且 without taking locks)

// hasLoadMethods 函数是根据 `headerType *mhdr` 的 `__objc_nlclslist` 区和 `__objc_nlcatlist` 区中是否有数据,
// 来判断是否有 +load 函数要执行。(即是否包含非懒加载类和非懒加载分类)
if (!hasLoadMethods((const headerType *)mh)) return;
// loadMethodLock 是一把递归互斥锁(加锁)
recursive_mutex_locker_t lock(loadMethodLock);

// Discover load methods
{
// runtimeLock 加锁
mutex_locker_t lock2(runtimeLock);
// 获取所有要调用的 +load 方法
prepare_load_methods((const headerType *)mh);
}

// Call +load methods (without runtimeLock - re-entrant)
// 调用获取到的所有 +load 方法
call_load_methods();
}

概括的说 load_images 函数就是用来调用类以及分类中的 +load 函数的;

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
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;

runtimeLock.assertLocked();

// GETSECT(_getObjc2NonlazyClassList, classref_t const, "__objc_nlclslist");
// 获取所有 __objc_nlclslist 区的数据(所有非懒加载类)

classref_t const *classlist = _getObjc2NonlazyClassList(mhdr, &count);

// class +load has been called
// #define RW_LOADED (1<<23)

// List of classes that need +load called (pending superclass +load)
// This list always has superclasses first because of the way it is constructed
// 由于其构造方式,此列表始终首先处理 superclasses 的 +load 函数
// 需要调用 +load 的 classes 列表
// static struct loadable_class *loadable_classes = nil;

// 遍历这些非懒加载类,并将其 +load 函数添加到 loadable_classes 数组中,优先添加其父类的 +load 方法,
// 用于下面 call_load_methods 函数调用
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}

// GETSECT(_getObjc2NonlazyCategoryList, category_t * const, "__objc_nlcatlist");
// 获取所有 __objc_nlcatlist 区的数据(所有非懒加载分类)

category_t * const *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

if (cls->isSwiftStable()) {
_objc_fatal("Swift class extensions and categories on Swift "
"classes are not allowed to have +load methods");
}

// 如果分类所属的类没有实现就先去实现
realizeClassWithoutSwift(cls, nil);

// 断言
ASSERT(cls->ISA()->isRealized());

// List of categories that need +load called (pending parent class +load)
// 需要调用 +load 的 categories 列表
// static struct loadable_category *loadable_categories = nil;

// 遍历这些分类,并将其 +load 方法添加到 loadable_categories 数组中保存
add_category_to_loadable_list(cat);
}
}

prepare_load_methods 用来获取所有要调用的 +load 方法(父类、子类、分类)。

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
/***********************************************************************
* prepare_load_methods
* Schedule +load for classes in this image, any un-+load-ed
* superclasses in other images, and any categories in this image.
**********************************************************************/
// Recursively schedule +load for cls and any un-+load-ed superclasses.
// cls must already be connected.
static void schedule_class_load(Class cls)
{
// 如果 cls 不存在则 return(下面有一个针对 superclass 的递归调用)
if (!cls) return;

// DEBUG 模式下的断言,cls 必须是实现过的(这个在 _read_images 中已经实现了)
ASSERT(cls->isRealized()); // _read_images should realize

// class +load has been called
// #define RW_LOADED (1<<23)

// RW_LOADED 是 class +load 已被调用的掩码
if (cls->data()->flags & RW_LOADED) return;

// Ensure superclass-first ordering
// 优先处理 superclass 的 +load 函数
schedule_class_load(cls->superclass);

// static struct loadable_class *loadable_classes = nil;
// struct loadable_class {
// Class cls; // may be nil
// IMP method;
// };

// 将 cls 的 +load 函数添加到全局的 loadable_class 数组 loadable_classes 中,
// loadable_class 结构体是用来保存类的 +load 函数的一个数据结构,其中 cls 是该类,method 则是 +load 函数的 IMP,
// 这里也能看出 +load 函数是不走 OC 的消息转发机制的,它是直接通过 +load 函数的地址调用的!
add_class_to_loadable_list(cls);

// 将 RW_LOADED 设置到类的 Flags 中
cls->setInfo(RW_LOADED);
}

schedule_class_load 将其 +load 方法添加到 loadable_classes 数组中;

1
schedule_class_load(cls->superclass);

优先添加其父类的 +load 方法。(用于后续的 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
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.
// 重入调用什么都不做;最外层的调用将完成工作。

// 如果正在 loading 则 return,
// 保证当前 +load 方法同时只有一次被调用
if (loading) return;
loading = YES;

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

// 循环调用
do {
// 1. Repeatedly call class +loads until there aren't any more

// 调用类中的 +load 函数
while (loadable_classes_used > 0) {
// 调用 loadable_classes 中的的类的 +load 函数,并且把 loadable_classes_used 置为 0
call_class_loads();
}

// 2. Call category +loads ONCE

// 调用 分类中的 +load 函数, 只调用一次 call_category_loads,因为上面的 call_class_loads 函数内部,
// 已经把 loadable_classes_used 置为 0,所以除非有新的分类需要 +load,即 call_category_loads 返回 true,
// 否则循环就结束了。
more_categories = call_category_loads();

// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
// 如果 loadable_classes_used 大于 0,或者有更多分类需要调用 +load,则循环继续。(一般 loadable_classes_used 到这里基本就是 0 了)

// 自动释放池进行 pop
objc_autoreleasePoolPop(pool);

// 标记处理完成了,可以进行下一个了
loading = NO;
}

call_load_methods,开始调用 +load 方法,在 while 循环中,先循环遍历类、父类的 +load 方法,然后遍历分类的 +load 方法,然后找到 @selector(load) 的实现并执行。
(*load_method)(cls, SEL_load) 代码就会调用 +[XXObject load] 方法。

load 方法是如何被调用的?

Objective-C 运行时初始化的时候,会通过 dyld_register_image_state_change_handler 在每次有新的镜像加入运行时的时候,进行回调。传入 load_images 将所有包含 load 方法的文件加入列表 loadable_classes,然后从这个列表中找到对应的 load 方法的实现,调用 load 方法。

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
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;

runtimeLock.assertLocked();

// GETSECT(_getObjc2NonlazyClassList, classref_t const, "__objc_nlclslist");
// 获取所有 __objc_nlclslist 区的数据(所有非懒加载类)

classref_t const *classlist = _getObjc2NonlazyClassList(mhdr, &count);

// class +load has been called
// #define RW_LOADED (1<<23)

// List of classes that need +load called (pending superclass +load)
// This list always has superclasses first because of the way it is constructed
// 由于其构造方式,此列表始终首先处理 superclasses 的 +load 函数
// 需要调用 +load 的 classes 列表
// static struct loadable_class *loadable_classes = nil;

// 遍历这些非懒加载类,并将其 +load 函数添加到 loadable_classes 数组中,优先添加其父类的 +load 方法,
// 用于下面 call_load_methods 函数调用
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}

// GETSECT(_getObjc2NonlazyCategoryList, category_t * const, "__objc_nlcatlist");
// 获取所有 __objc_nlcatlist 区的数据(所有非懒加载分类)

category_t * const *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

if (cls->isSwiftStable()) {
_objc_fatal("Swift class extensions and categories on Swift "
"classes are not allowed to have +load methods");
}

// 如果分类所属的类没有实现就先去实现
realizeClassWithoutSwift(cls, nil);

// 断言
ASSERT(cls->ISA()->isRealized());

// List of categories that need +load called (pending parent class +load)
// 需要调用 +load 的 categories 列表
// static struct loadable_category *loadable_categories = nil;

// 遍历这些分类,并将其 +load 方法添加到 loadable_categories 数组中保存
add_category_to_loadable_list(cat);
}
}

/***********************************************************************
* prepare_load_methods
* Schedule +load for classes in this image, any un-+load-ed
* superclasses in other images, and any categories in this image.
**********************************************************************/
// Recursively schedule +load for cls and any un-+load-ed superclasses.
// cls must already be connected.
static void schedule_class_load(Class cls)
{
// 如果 cls 不存在则 return(下面有一个针对 superclass 的递归调用)
if (!cls) return;

// DEBUG 模式下的断言,cls 必须是实现过的(这个在 _read_images 中已经实现了)
ASSERT(cls->isRealized()); // _read_images should realize

// class +load has been called
// #define RW_LOADED (1<<23)

// RW_LOADED 是 class +load 已被调用的掩码
if (cls->data()->flags & RW_LOADED) return;

// Ensure superclass-first ordering
// 优先处理 superclass 的 +load 函数
schedule_class_load(cls->superclass);

// static struct loadable_class *loadable_classes = nil;
// struct loadable_class {
// Class cls; // may be nil
// IMP method;
// };

// 将 cls 的 +load 函数添加到全局的 loadable_class 数组 loadable_classes 中,
// loadable_class 结构体是用来保存类的 +load 函数的一个数据结构,其中 cls 是该类,method 则是 +load 函数的 IMP,
// 这里也能看出 +load 函数是不走 OC 的消息转发机制的,它是直接通过 +load 函数的地址调用的!
add_class_to_loadable_list(cls);

// 将 RW_LOADED 设置到类的 Flags 中
cls->setInfo(RW_LOADED);
}

从源码中可以看出,

  • 通过 _getObjc2NonlazyClassList 获取所有的类的列表之后,会通过 remapClass 获取类对应的指针,然后调用 schedule_class_load 递归把当前类和没有调用 + load 方法的父类,通过 add_class_to_loadable_list(cls) 方法添加到 loadable_classes 列表中,保证父类在子类前调用 +load 方法
  • 通过 _getObjc2NonlazyCategoryList 获取所有的分类的列表之后,会通过 remapClass 获取分类对应的指针,然后调用add_category_to_loadable_list 把当前分类添加到 loadable_categories 列表中。

在将镜像加载到运行时、对 +load 方法的准备就绪之后,执行 call_load_methods,开始调用 +load 方法:

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
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.
// 重入调用什么都不做;最外层的调用将完成工作。

// 如果正在 loading 则 return,
// 保证当前 +load 方法同时只有一次被调用
if (loading) return;
loading = YES;

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

// 循环调用
do {
// 1. Repeatedly call class +loads until there aren't any more

// 调用类中的 +load 函数
while (loadable_classes_used > 0) {
// 调用 loadable_classes 中的的类的 +load 函数,并且把 loadable_classes_used 置为 0
call_class_loads();
}

// 2. Call category +loads ONCE

// 调用 分类中的 +load 函数, 只调用一次 call_category_loads,因为上面的 call_class_loads 函数内部,
// 已经把 loadable_classes_used 置为 0,所以除非有新的分类需要 +load,即 call_category_loads 返回 true,
// 否则循环就结束了。
more_categories = call_category_loads();

// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
// 如果 loadable_classes_used 大于 0,或者有更多分类需要调用 +load,则循环继续。(一般 loadable_classes_used 到这里基本就是 0 了)

// 自动释放池进行 pop
objc_autoreleasePoolPop(pool);

// 标记处理完成了,可以进行下一个了
loading = NO;
}
  • 不停调用类的 +load 方法,直到 loadable_classes 为空。
  • 然后调用一次 call_category_loads 加载分类。
  • 如果有 loadable_classes 或者更多的分类,继续调用 +load 方法。
  • 类先于分类调用

其中 call_class_loads 会从一个待加载的类列表 loadable_classes 中寻找对应的类,然后通过对应的 SEL ,找到 IMP 的实现并执行。

load 方法的应用

由于调用 +load 方法运行时间过早,环境很不安全,我们应该尽量减少 +load 方法的逻辑。另一个原因是load方法是线程安全的,它内部使用了锁,所以我们应该避免线程阻塞在 +load 方法中。不过在这个时间点,所有的 framework 都已经加载到了运行时中,所以调用 framework 中的方法都是安全的。

Initialize

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

调用顺序

先调用父类的 +initialize 方法,再调用子类的 +initialize 方法;
(先初始化父类,再初始化子类,每个类只会初始化1次)

For Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#import <Foundation/Foundation.h>

@interface Person : NSObject @end

@implementation Person

+ (void)initialize {
NSLog(@"Person initialize");
}

@end

int main(int argc, const char * argv[]) {
@autoreleasepool { }
return 0;
}

程序运行之后控制台没有任何输出;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#import <Foundation/Foundation.h>

@interface Person : NSObject @end

@implementation Person

+ (void)initialize {
NSLog(@"Person initialize");
}

@end

int main(int argc, const char * argv[]) {
@autoreleasepool {
[Person alloc];
}
return 0;
}

程序运行之后没有直接调用 +initialize 方法。但是,这里也打印出了 Person initialize 字符串;
因此得出结论:+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
@interface Person : NSObject

@end

@implementation Person

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

@end

@interface Student : Person

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

int main(int argc, const char * argv[]) {
@autoreleasepool {
[Student alloc];
}
return 0;
}

控制台打印结果如下:

1
2
+[Person initialize]
+[Student initialize]

因此得出结论:
先调用父类的 +initialize 方法,再调用子类的 +initialize 方法;(先初始化父类,再初始化子类,每个类只会初始化1次)

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
@interface Person : NSObject

@end

@implementation Person

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

@end

@implementation Person (Test1)

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

@interface Student : Person

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

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

int main(int argc, const char * argv[]) {
@autoreleasepool {
[Student alloc];
}
return 0;
}

控制台打印结果如下:

1
2
+[Person(Test1) initialize]
+[Student(Test1) initialize]

因此得出结论:
+initialize方法 是通过 objc_msgSend 进行调用的,分类的加载顺序取决于编译的顺序,最后面参与编译的分类优先调用。

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
@interface Person : NSObject

@end

@implementation Person

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

@end

@interface Student : Person

@end
@implementation Student

@end

int main(int argc, const char * argv[]) {
@autoreleasepool {
[Student alloc];
}
return 0;
}

控制台打印结果如下:

1
2
+[MJPerson initialize]
+[MJPerson initialize]

可见当子类未实现 initialize 方法,会调用父类 initialize 方法.
由于子类会继承父类的 initialize 方法,即使子类没有实现 initialize 方法,也会调用父类的方法,这会导致一个很严重的问题:
运行后发现父类的 initialize 方法竟然调用了两次:

虽然 initialize 方法对一个类而言只会调用一次,但这里由于出现了两个类,所以调用两次符合规则,但不符合我们的需求。正确使用initialize方法的姿势如下:

1
2
3
4
5
6
// In Parent.m
+ (void)initialize {
if (self == [Parent class]) {
NSLog(@"Initialize Parent, caller Class %@", [self class]);
}
}

加上判断后,就不会因为子类而调用到自己的 initialize 方法了。

底层原理分析

initialize 方法中打一个断点,来查看这个方法的调用栈:

image.png

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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
/***********************************************************************
* lookUpImpOrForward.
* The standard IMP lookup.
* Without LOOKUP_INITIALIZE: tries to avoid +initialize (but sometimes fails)
* Without LOOKUP_CACHE: skips optimistic unlocked lookup (but uses cache elsewhere)
* Most callers should use LOOKUP_INITIALIZE and LOOKUP_CACHE
* inst is an instance of cls or a subclass thereof, or nil if none is known.
* If cls is an un-initialized metaclass then a non-nil inst is faster.
* May return _objc_msgForward_impcache. IMPs destined for external use
* must be converted to _objc_msgForward or _objc_msgForward_stret.
* If you don't want forwarding at all, use LOOKUP_NIL.
**********************************************************************/
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
Class curClass;
IMP methodPC = nil;
Method meth;
bool triedResolver = NO;

methodListLock.assertUnlocked();

// Optimistic cache lookup
if (behavior & LOOKUP_CACHE) {
methodPC = _cache_getImp(cls, sel);
if (methodPC) goto out_nolock;
}

// Check for freed class
if (cls == _class_getFreedObjectClass())
return (IMP) _freedHandler;

// Check for +initialize
if ((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized()) {
initializeNonMetaClass (_class_getNonMetaClass(cls, inst));
// If sel == initialize, initializeNonMetaClass 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
}

// The lock is held to make method-lookup + cache-fill atomic
// with respect to method addition. Otherwise, a category could
// be added but ignored indefinitely because the cache was re-filled
// with the old value after the cache flush on behalf of the category.
retry:
methodListLock.lock();

// Try this class's cache.

methodPC = _cache_getImp(cls, sel);
if (methodPC) goto done;

// Try this class's method lists.

meth = _class_getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, cls, meth, sel);
methodPC = method_getImplementation(meth);
goto done;
}

// Try superclass caches and method lists.

curClass = cls;
while ((curClass = curClass->superclass)) {
// Superclass cache.
meth = _cache_getMethod(curClass, sel, _objc_msgForward_impcache);
if (meth) {
if (meth != (Method)1) {
// Found the method in a superclass. Cache it in this class.
log_and_fill_cache(cls, curClass, meth, sel);
methodPC = method_getImplementation(meth);
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
}

// Superclass method list.
meth = _class_getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, curClass, meth, sel);
methodPC = method_getImplementation(meth);
goto done;
}
}

// No implementation found. Try method resolver once.

if ((behavior & LOOKUP_RESOLVER) && !triedResolver) {
methodListLock.unlock();
_class_resolveMethod(cls, sel, inst);
triedResolver = YES;
goto retry;
}

// No implementation found, and method resolver didn't help.
// Use forwarding.

_cache_addForwardEntry(cls, sel);
methodPC = _objc_msgForward_impcache;

done:
methodListLock.unlock();

out_nolock:
if ((behavior & LOOKUP_NIL) && methodPC == (IMP)_objc_msgForward_impcache) {
return nil;
}
return methodPC;
}

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver) 实现了一套查找 IMP 的标准路径,也就是在消息转发(Forward)之前的逻辑。

lookUpImpOrForward 接着做了如下两件事:

如果使用缓存(cache 参数为 YES),那就调用 cache_getImp 方法从缓存查找 IMPcache_getImp 是用汇编语言写的,也可以在 objc-msg-x86_64.s 找到。
如果是第一次用到这个类且 initialize 参数为 YES(initialize && !cls->isInitialized()),来判断当前类是否初始化过,如果存在父类,并且父类没有初始化,通过递归初始化父类。

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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/***********************************************************************
* class_initialize. Send the '+initialize' message on demand to any
* uninitialized class. Force initialization of superclasses first.
**********************************************************************/
void initializeNonMetaClass(Class cls)
{
ASSERT(!cls->isMetaClass());

Class supercls;
bool reallyInitialize = NO;

// Make sure super is done initializing BEFORE beginning to initialize cls.
// See note about deadlock above.
supercls = cls->superclass;
if (supercls && !supercls->isInitialized()) {
initializeNonMetaClass(supercls);
}

// Try to atomically set CLS_INITIALIZING.
SmallVector<_objc_willInitializeClassCallback, 1> localWillInitializeFuncs;
{
monitor_locker_t lock(classInitLock);
if (!cls->isInitialized() && !cls->isInitializing()) {
cls->setInitializing();
reallyInitialize = YES;

// Grab a copy of the will-initialize funcs with the lock held.
localWillInitializeFuncs.initFrom(willInitializeFuncs);
}
}

if (reallyInitialize) {
// We successfully set the CLS_INITIALIZING bit. Initialize the class.

// Record that we're initializing this class so we can message it.
_setThisThreadIsInitializingClass(cls);

if (MultithreadedForkChild) {
// LOL JK we don't really call +initialize methods after fork().
performForkChildInitialize(cls, supercls);
return;
}

for (auto callback : localWillInitializeFuncs)
callback.f(callback.context, cls);

// Send the +initialize message.
// Note that +initialize is sent to the superclass (again) if
// this class doesn't implement +initialize. 2157218
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: calling +[%s initialize]",
objc_thread_self(), cls->nameForLogging());
}

// Exceptions: A +initialize call that throws an exception
// is deemed to be a complete and successful +initialize.
//
// Only __OBJC2__ adds these handlers. !__OBJC2__ has a
// bootstrapping problem of this versus CF's call to
// objc_exception_set_functions().
#if __OBJC2__
@try
#endif
{
callInitialize(cls);

if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
objc_thread_self(), cls->nameForLogging());
}
}
#if __OBJC2__
@catch (...) {
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: +[%s initialize] "
"threw an exception",
objc_thread_self(), cls->nameForLogging());
}
@throw;
}
@finally
#endif
{
// Done initializing.
lockAndFinishInitializing(cls, supercls);
}
return;
}

else if (cls->isInitializing()) {
// We couldn't set INITIALIZING because INITIALIZING was already set.
// If this thread set it earlier, continue normally.
// If some other thread set it, block until initialize is done.
// It's ok if INITIALIZING changes to INITIALIZED while we're here,
// because we safely check for INITIALIZED inside the lock
// before blocking.
if (_thisThreadIsInitializingClass(cls)) {
return;
} else if (!MultithreadedForkChild) {
waitForInitializeToComplete(cls);
return;
} else {
// We're on the child side of fork(), facing a class that
// was initializing by some other thread when fork() was called.
_setThisThreadIsInitializingClass(cls);
performForkChildInitialize(cls, supercls);
}
}

else if (cls->isInitialized()) {
// Set CLS_INITIALIZING failed because someone else already
// initialized the class. Continue normally.
// NOTE this check must come AFTER the ISINITIALIZING case.
// Otherwise: Another thread is initializing this class. ISINITIALIZED
// is false. Skip this clause. Then the other thread finishes
// initialization and sets INITIALIZING=no and INITIALIZED=yes.
// Skip the ISINITIALIZING clause. Die horribly.
return;
}

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

如果存在父类,并且父类没有初始化,通过递归初始化父类。

1
2
3
4
supercls = cls->superclass;
if (supercls && !supercls->isInitialized()) {
initializeNonMetaClass(supercls);
}

initializeNonMetaClass 方法的主要作用自然是向未初始化的类发送 +initialize 消息,不过会通过递归向父类先发送 +initialize 消息。

1
2
3
4
5
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
asm("");
}

向当前类发送 +initialize 消息。
验证结果:
先调用父类的 +initialize 方法,再调用子类的 +initialize 方法;(先初始化父类,再初始化子类,每个类只会初始化1次)

1
2
3
4
5
6
7
8
monitor_locker_t lock(classInitLock);
if (!cls->isInitialized() && !cls->isInitializing()) {
cls->setInitializing();
reallyInitialize = YES;

// Grab a copy of the will-initialize funcs with the lock held.
localWillInitializeFuncs.initFrom(willInitializeFuncs);
}

通过加锁来设置 CLS_INITIALIZING 标志位;

/***

  • lockAndFinishInitializing
  • Mark a class as finished initializing and notify waiters, or queue for later.
  • If the superclass is also done initializing, then update
  • the info bits and notify waiting threads.
  • If not, update them later. (This can happen if this +initialize
  • was itself triggered from inside a superclass +initialize.)
  • *****/
    static void lockAndFinishInitializing(Class cls, Class supercls)
    {
    monitor_locker_t lock(classInitLock);
    if (!supercls || supercls->isInitialized()) {
    _finishInitializing(cls, supercls);
    } else {
    _finishInitializingAfter(cls, supercls);
    }
    }

完成初始化,如果父类已经初始化完成,设置 CLS_INITIALIZING 标志位。否则,在父类初始化完成之后再设置标志位。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// We couldn't set INITIALIZING because INITIALIZING was already set.
// If this thread set it earlier, continue normally.
// If some other thread set it, block until initialize is done.
// It's ok if INITIALIZING changes to INITIALIZED while we're here,
// because we safely check for INITIALIZED inside the lock
// before blocking.
if (_thisThreadIsInitializingClass(cls)) {
return;
} else if (!MultithreadedForkChild) {
waitForInitializeToComplete(cls);
return;
} else {
// We're on the child side of fork(), facing a class that
// was initializing by some other thread when fork() was called.
_setThisThreadIsInitializingClass(cls);
performForkChildInitialize(cls, supercls);
}

如果当前线程正在初始化当前类,直接返回,否则,会等待其它线程初始化结束后,再返回,保证线程安全。

Initialize 方法的应用

由于 initialize 作用也非常局限,一般我们只会在 initialize 方法中进行一些常量的初始化。

+initialize 和 +load 的很大区别是

+initialize+load 的很大区别是,+initialize 是通过 objc_msgSend 进行调用的,所以有以下特点:

  • 如果子类没有实现 +initialize,会调用父类的 +initialize注意父类的 +initialize 方法可能会被调用多次,不代表父类初始化多次)。

  • 如果分类实现了 +initialize,就覆盖类本身的 +initialize 调用。

  • 调用方式
    1> +load 是根据函数地址直接调用
    2> initialize 是通过 objc_msgSend 调用

  • 调用时刻
    1> loadruntime 加载类、分类的时候调用(只会调用1次)
    2> initialize 是类第一次接收到消息的时候调用,每一个类只会 initialize 一次(父类的 initialize 方法可能会被调用多次)

load、initialize的调用顺序?

  • load
    1> 先调用类的 load
    a) 先编译的类,优先调用 load
    b) 调用子类的 load 之前,会先调用父类的 load

2> 再调用分类的 load
a) 先编译的分类,优先调用 load

  • initialize
    1> 先初始化父类
    2> 再初始化子类(可能最终调用的是父类的 initialize 方法)