Objective-C 中 initialize load

我们接着上篇 Class-Model 继续围绕着 Runtime 来更深入的了解一个继承与 NSObject 类的初始化的过程。

load

我们先来了解下 load 方法,当一个类或者分类添加到运行时时候会被调用,通常的用法是在类中实现这个方法然后我们去做 Method Swizzling 操作,实质上就是可以在运行时干预类的加载。通过简单的Demo我们可以发现,一个类和她的子类都会执行 load 方法,而且子类会后置执行 load 方法。同时,分类中的 load 方法会先于主类中的。

prepare_load_methods

我们可以找到 objc-runtime-new.mm 中的 prepare_load_methods 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;

runtimeLock.assertLocked();

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

prepare_load_methods 方法中,分为两个步骤:

  • 获取了所有类后,遍历列表,将其中有 load 方法的类加入 loadable_class
  • 获取所有的分类,遍历列表,将其中有 load 方法的类加入 loadable_categories

其主要的目的就是提前准备好满足 load 方法调用条件的类和分类,以供下一步使用。

schedule_class_load

我们再来看下预加载方法中会首先通过 schedule_class_load(cls->superclass) 的递归调用确保有父类中的 load 方法被加入 loadable_class,从而保证父类的 load 方法总是在子类之前调用。因此,在覆写+load方法时,不需要调用super方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
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);
}

call_load_methods

下面就是对 load 方法的调用了,打开objc-loadmethod.mm,找到 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
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;
}

调用类的 load 方法,在 call_class_loads 方法中通过在第一步读取 prepare_load_methods 步骤里的 loadable_classes ,遍历列表并调用 load 方法,然后遍历的调用分类的 load 方法。

call_class_loads

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

这个函数负责调用类的 load 方法,它从全局变量 loadable_classes 中取出可供调用的类之后进行重置操作。

1
2
3
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;

loadable_classes 指向用于保存类信息的内存的首地址,loadable_classes_allocated 标识已分配的内存空间大小,loadable_classes_used 标识已使用的内存空间大小。

循环调用所有类的 load 方法,这里是直接使用函数内存地址的方式 (*load_method)(cls, SEL_load);load 方法进行调用的,而不是使用发送消息 objc_msgSend 的方式。

真是因为这种调用方法,所以父类、子类和分类中的 load 方法才会有之前提到的逻辑,子类没实现 load 方法的话在 runtime 时期也不会去调用父类的 load 方法,如果都实现则都会调用。

initialize

initialize 方法是在类和子类在第一次收到实例方法和类方法的调用消息时候调用,因此这个方法是懒加载形式调用的,如果程序一直没有给一个类发送消息那么 initialize 则永远不会被调用。

lookUpImpOrForward

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
bool initialize, bool cache, bool resolver)
{
...

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 进行初始化。

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

// 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]",
pthread_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]",
pthread_self(), cls->nameForLogging());
}
}
#if __OBJC2__
@catch (...) {
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: +[%s initialize] "
"threw an exception",
pthread_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!");
}
}

_class_initialize 方法实现看起来比较长,但其实关键步骤也就只有两步:

  • 11-13行使用了递归确保父类先于子类初始化。
  • 通过判断此类的初始化状态进行相应处理。

初始化状态处理

未初始化

  1. 如果当前类未初始化,则会向它发送一个 setInitializing 消息,将该类的元类的信息更改为 CLS_INITIALIZING,并通过reallyInitialize 标识来与 Initializing 区分。
  2. 设置成功 CLS_INITIALIZING 后,_setThisThreadIsInitializingClass 记录当前线程正在初始化当前类,当前线程可以向该类发送消息,而其他线程则需要等待。
  3. 通过 callInitialize 调用 initialize 方法,也就是说与 load 不同该方法是通过 objc_msgSend 发送消息实现的,因此也拥有 objc_msgSend 带来的特性,也就是说子类会继承父类的方法实现,而分类的实现也会覆盖元类。
  4. initialize 完成后,更新当前类的状态。如果父类已经完成初始化,则 _finishInitializing 立马更新,否则通过_finishInitializingAfter 等父类完成后再更新。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static void _finishInitializingAfter(Class cls, Class supercls)
{
PendingInitialize *pending;

classInitLock.assertLocked();

if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: class %s will be marked as fully "
"+initialized after superclass +[%s initialize] completes",
pthread_self(), cls->nameForLogging(),
supercls->nameForLogging());
}

if (!pendingInitializeMap) {
pendingInitializeMap = NXCreateMapTable(NXPtrValueMapPrototype, 10);
// fixme pre-size this table for CF/NSObject +initialize
}

pending = (PendingInitialize *)malloc(sizeof(*pending));
pending->subclass = cls;
pending->next = (PendingInitialize *)
NXMapGet(pendingInitializeMap, supercls);
NXMapInsert(pendingInitializeMap, supercls, pending);
}

在该方法中,通过声明一个 PendingInitialize 类型的结构体 pending 来存储当前类与父类信息,并以父类 supercls 为key值,以 pending 为value存储在 pendingInitializeMap 链表中。

  1. 如果完成初始化则调用 _finishInitializing,在该方法中会首先将当前类标记为已完成初始化状态 Initialized,然后去读取 pendingInitializeMap,如果查找到该类对应的待处理子类,则将对应的消息移除并通过递归的方法将因当前类而被阻塞的子类标记为已完成初始化。
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
static void _finishInitializing(Class cls, Class supercls)
{
PendingInitialize *pending;

classInitLock.assertLocked();
assert(!supercls || supercls->isInitialized());

if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: %s is fully +initialized",
pthread_self(), cls->nameForLogging());
}

// mark this class as fully +initialized
cls->setInitialized();
classInitLock.notifyAll();
_setThisThreadIsNotInitializingClass(cls);

// mark any subclasses that were merely waiting for this class
if (!pendingInitializeMap) return;
pending = (PendingInitialize *)NXMapGet(pendingInitializeMap, cls);
if (!pending) return;

NXMapRemove(pendingInitializeMap, cls);

// Destroy the pending table if it's now empty, to save memory.
if (NXCountMapTable(pendingInitializeMap) == 0) {
NXFreeMapTable(pendingInitializeMap);
pendingInitializeMap = nil;
}

while (pending) {
PendingInitialize *next = pending->next;
if (pending->subclass) _finishInitializing(pending->subclass, cls);
free(pending);
pending = next;
}
}

正在初始化

如果是当前线程在进行初始化,则不做处理。
如果是其他线程在进行初始化,则等其他线程完成后再返回,以保证线程安全。

已初始化

如果已经完成初始化,则不做处理。

不知道什么状态

还没有遇到过,但是一点控制流走到这一步会抛出一个错误,提示线程安全类在运行时的初始化有问题。

initialize 总结

  1. initialize 方法是在main函数之后调用的;
  2. initialize 方法遵从懒加载方式,只有在类或它的子类收到第一条消息之前被调用的;
  3. 子类中不需要调用 super 方法,会自动调用父类的方法实现;
  4. initialize 只调用一次,init可多次调用。

initialize与load对比

load initialize
调用时机 main之前,runtime初始化时 main之后,当前类或子类收到第一条消息前
调用方式 函数内存地址 objc_msgSend
继承父类实现
分类实现 类和分类都执行 覆盖父类实现
调用顺序 父类-子类-分类 父类-子类
调用次数 1次 1次或不调用

参考链接