iOS开发-+initialize和+load方法

之前在看项目组件化方案的时候,看到蘑菇街的组件化方案中有在load方法中注册vc的方法。

所以想要完整了解一下load方法的具体细节,另外发现initialize方法基本上和load是成对出现的,所以顺便就一起看一下。

initialize

官方文档

Initializes the class before it receives its first message.
(在类收到第一条消息之前初始化它。)

官方文档中对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
78
79
80
81
82
83
84
85
86
87
88
89
90
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
//如果initialize==Yes,说明需要初始化,并且该类没有进行过初始化,然后调用_class_initialize进行初始化
if (initialize && !cls->isInitialized()) {
runtimeLock.unlockRead();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.read();
}
}
void _class_initialize(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()) {
_class_initialize(supercls);
}
// Try to atomically set CLS_INITIALIZING.
{
monitor_locker_t lock(classInitLock);
if (!cls->isInitialized() && !cls->isInitializing()) {
cls->setInitializing();
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.
_setThisThreadIsInitializingClass(cls);
if (MultithreadedForkChild) {
// LOL JK we don't really call +initialize methods after fork().
performForkChildInitialize(cls, supercls);
return;
}
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: calling +[%s initialize]",
pthread_self(), cls->nameForLogging());
}
//调用初始化发送,
callInitialize(cls);
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
pthread_self(), cls->nameForLogging());
}
return;
}
else if (cls->isInitializing()) {
if (_thisThreadIsInitializingClass(cls)) {
return;
} else if (!MultithreadedForkChild) {
waitForInitializeToComplete(cls);
return;
} else {
_setThisThreadIsInitializingClass(cls);
performForkChildInitialize(cls, supercls);
}
}
else if (cls->isInitialized()) {
return;
}
else {
// We shouldn't be here.
_objc_fatal("thread-safe class init in objc runtime is buggy!");
}
}
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");
}

可以看到,调用时会先去查看父类是否存在,并且父类是否有初始化过,如果满足条件之后,就会递归去调用

1
2
3
if (supercls && !supercls->isInitialized()) {
_class_initialize(supercls);
}

最后可以看到调用了一个方法callInitialize(cls)
仔细看这个方法,其实内部就是一个objc_msgSend

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

方法实现

接下来我们通过一些代码来看一下initialize的执行顺序。

首先我们创建一个类ZZRInitizial,然后重写一下他的initialize方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface ZZRInitizial : NSObject
@end
NS_ASSUME_NONNULL_END
--------------------------------------------------------------
#import "ZZRInitizial.h"
@implementation ZZRInitizial
+ (void)initialize
{
NSLog(@"%s",__FUNCTION__);
}
@end

然后运行一下,发现,什么都没有打印,不过因为之前看过文档和源码,initialize是使用懒加载调用的,所以当我们没有用到它的时候没有打印也很正常。接下来我们试一下初始化一个实例。

1
[ZZRInitizial new];

可以看到现在就打印出来了

1
2019-05-05 13:58:26.566212+0800 initializeDemo[86317:3926410] +[ZZRInitizial initialize]

接下来我们定义一个继承了ZZRInitizial的子类ZZRInitizialSubClass,然后再调用子类的初始化实例。

1
[ZZRInitizialSubClass new];

然后可以看到打印出的方法

1
2
2019-05-05 14:02:42.331628+0800 initializeDemo[86412:3934965] +[ZZRInitizial initialize]
2019-05-05 14:02:42.331745+0800 initializeDemo[86412:3934965] +[ZZRInitizialSubClass initialize]

接下来我们再创建一个ZZRInitizial的Category,然后初始化一个实例

1
2019-05-05 14:22:32.726589+0800 initializeDemo[86608:3962561] +[ZZRInitizial(myCategory) initialize]

我们会发现调用的是Category中的方法,而没有调用原本类中的方法了。

总结

所以我们可以看到,initialize在类或者其子类的第一个方法被调用之前调用,并且使用懒加载的调用方式,即没有使用就不会调用。
并且因为是有系统调用,所以不需要再调用[super initialize]
调用顺序上,会先调用父类的initialize,然后再调用原有类的initialize,如果有Category,并且Category中重写了initialize方法,则会调用Category中的initialize

load

官方文档

Invoked whenever a class or category is added to the Objective-C runtime; implement this method to perform class-specific behavior upon loading.
(当类或者类别加入runtime时,实现该方法,可以在类加载的时候做一些类特有的操作)

以上是官方文档中对load方法的描述,也就是说当类被加入runtime,也就是被引用的时候,就会实现该方法。
另外,在每一个类、分类在程序运行的过程中除了手动调用之外,只会调用一次。

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
{
rwlock_writer_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
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
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
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertWriting();
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);
}
}
//递归查找父类,父类优先添加到集合中
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);
}
//把类和类的load方法添加到loadable_classes数组中,数组中每一个元素都是一个结构体,结构体中包含类和load方法的IMP
static struct loadable_class *loadable_classes = nil;
struct loadable_class {
Class cls; // may be nil
IMP method;
};
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));
}
loadable_classes[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
loadable_classes_used++;
}
static struct loadable_category *loadable_categories = nil;
struct loadable_category {
Category cat; // may be nil
IMP method;
};
//处理分类
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++;
}

从上边这段代码的函数名就可以看出来,主要是准备load方法。
这段代码处理了类的load方法,递归并获取父类的,把类和类的load方法的IMP存储到struct loadable_class结构体中,并把结构体添加到了loadable_classes数组中。
另外处理分类load方法,把分类和分类的load方法的IMP存储到loadable_category结构体中,并把结构体添加到了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
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
126
127
128
129
130
131
132
133
134
135
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 {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
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;
}
static void call_class_loads(void)
{
int i;
// Detach current loadable list.
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);
}
static bool call_category_loads(void)
{
int i, shift;
bool new_categories_added = NO;
// Detach current loadable list.
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;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Category cat = cats[i].cat;
load_method_t load_method = (load_method_t)cats[i].method;
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);
cats[i].cat = nil;
}
}
// Compact detached list (order-preserving)
shift = 0;
for (i = 0; i < used; i++) {
if (cats[i].cat) {
cats[i-shift] = cats[i];
} else {
shift++;
}
}
used -= shift;
// Copy any new +load candidates from the new list to the detached list.
new_categories_added = (loadable_categories_used > 0);
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];
}
// Destroy the new list.
if (loadable_categories) free(loadable_categories);
// Reattach the (now augmented) detached list.
// But if there's nothing left to load, destroy the list.
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;
}

这段代码主要调用了类的load方法,优先调用父类的load方法,然后调用分类的load方法。
调用父类的load方法主要是通过(*load_method)(cls, SEL_load);方法直接调用指针。
但是手动调用和自动调用的区别就是手动调用的时候是通过objc_msgSend而不是指针。

方法实现

同样的,我们新建一个类ZZRLoad

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface ZZRLoad : NSObject
@end
NS_ASSUME_NONNULL_END
----------------------------------------
#import "ZZRLoad.h"
@implementation ZZRLoad
+ (void)load
{
NSLog(@"%s",__FUNCTION__);
}
@end

然后运行代码,我们就会发现,ZZRLoadload方法就已经执行了,而且是在最前边执行的。

1
2019-05-05 15:34:56.613609+0800 initializeDemo[87221:4058674] +[ZZRLoad load]

接下来基于ZZRLoad创建一个子类ZZRLoadSubClass,然后运行会发现:

1
2
2019-05-05 15:38:25.778688+0800 initializeDemo[87271:4065322] +[ZZRLoad load]
2019-05-05 15:38:25.779203+0800 initializeDemo[87271:4065322] +[ZZRLoadSubClass load]

两个方法都执行了load。

然后基于ZZRLoad创建一个分类,运行后会发现:

1
2
3
2019-05-05 15:39:26.135793+0800 initializeDemo[87297:4067390] +[ZZRLoad load]
2019-05-05 15:39:26.136449+0800 initializeDemo[87297:4067390] +[ZZRLoadSubClass load]
2019-05-05 15:39:26.136542+0800 initializeDemo[87297:4067390] +[ZZRLoad(myCategory) load]

三个也都执行了。

总结

Load方法会在runtime加载类、分类的时候调用,每个类、分类的load方法在程序运行的过程中只会调用一次(你手动调用的不算数)。由于load函数是系统自动加载的,因此不需要调用父类的load函数,否则父类的load函数会多次执行。

区别

系统中为了保障线程安全,在load方法内部使用了锁,所以我们在使用的时候尽量需要在load方法中添加太多逻辑,防止线程阻塞。
对弈initialize方法中主要用来对一些不方便在编译期初始化的对象进行赋值。

参考资料

load - NSObject | Apple Developer Documentation
initialize - NSObject | Apple Developer Documentation
iOS类方法load和initialize详解 - 掘金
【OC底层】Category、+load方法、+initialize方法原理 - 这酸爽! - 博客园
iOS - + initialize 与 +load - 简书
通过源码查看load和initialize - 简书