Hello, World!

load、initialize

字数统计: 3k阅读时长: 13 min
2018/07/05 Share

load

基本使用

load方法是runtime在加载类和分类的时候会调用,是在程序入口函数main之前调用,而且只会调用一次。

通过代码验证一下本类的load方法调用。

我们添加Panda继承Animal类,并添加Panda+One分类,分别重写+load方法。

1
2
3
Animal: load
Panda: load
Panda (One): load

通过验证我们发现不仅调用了分类的load方法,而且调用了原类的load方法,这和上面我们验证的优先调用分类的方法的逻辑相冲突,到底为什么会调用原类的方法,我们通过底层的源码一探究竟。

调用原理

同样的我们从runtime的入口_objc_init函数的load_images函数寻找答案:

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

// images指是镜像、模块
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

↓↓↓
最终来到了call_load_methods函数

objc源码路径:runtime/objc-loadmethod.mm

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
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) {
// 先调用本类的load方法
call_class_loads();
}

// 2. Call category +loads ONCE
// 再调用分类的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方法:

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

for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
// 得到load方法的地址
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方法
(*load_method)(cls, @selector(load));
}

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

↓↓↓
再调用分类的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
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方法地址
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方法
(*load_method)(cls, @selector(load));
cats[i].cat = nil;
}
}

...
...
...
}

runtime的消息发送机制不同的是,消息发送机制是需要通过isa指针去逐层寻找,而load方法是不需要通过消息发送的,而是直接通过函数的地址来调用的

1
2
3
4
5
6
7
8
9
struct loadable_class {
Class cls; // may be nil
IMP method; // 函数实现地址,指向的是原类的load方法
};

struct loadable_category {
Category cat; // may be nil
IMP method; // 函数的实现地址,指向的是分类的load方法
};

调用顺序

即使是再复杂的继承关系,原类、分类、子类的load方法都会被调用,并且是按照一定的顺序调用的。

通过上面的源码可以看到在调用原类和分类的load方法的时候,都是分别通过一个数组loadable_classesloadable_categories进行for循环去遍历的,所以知道数组的顺序,就可以知道方法的调用顺序。

我们回到之前的入口函数load_images,发现在调用call_load_methods()函数之前调用了prepare_load_methods()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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();
}

↓↓↓

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

runtimeLock.assertLocked();

// 1. 先遍历所有的原类的的load方法
// 这个顺序是有编译顺序决定的,可以手动设置顺序
classref_t const *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
// 1.1 先将父类的load方法进行添加
// 1.2 再将子类的load方法进行添加
schedule_class_load(remapClass(classlist[i]));
}

// 2. 再遍历分类的load方法
// 这个顺序也是有编译顺序决定的,可以手动设置编译顺序
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());
add_category_to_loadable_list(cat);
}
}

↓↓↓
原类的load方法添加到loadable_classes数组的顺序:

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
// 1. 会先添加父类的load方法
static void schedule_class_load(Class cls)
{
if (!cls) return;
ASSERT(cls->isRealized()); // _read_images should realize

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

// 先通过递归调用
// 将父类的load方法添加到loadable_classes数组
schedule_class_load(cls->superclass);

// 再将子类的cls添加到loadable_classes数组的
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}

// 2. 添加到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));
}

loadable_classes[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
loadable_classes_used++;
}

分类的load方法添加到loadable_categories数组的顺序:

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
// 直接添加到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++;
}

通过以上源码的逻辑处理,我们发现数组的添加顺序导致了load方法的调用顺序:先将元类添加到数组,同时会先去将父类添加到数组,再讲子类添加到数组,最后将分类添加到数组。所以在调用load方法的时候也会是按照类的添加顺序来调用。

总结

1,先调用所有原类的load方法
   按照编译顺序调用(可以手动设置编译顺序)
   调用子类的load之前会先调用父类的load方法
2,再调用分类的load方法
   按照编译顺序调用(可以手动设置编译顺序)
   
Category中有load方法,load方法在程序加载了类和分类的时候就会调用,在main函数之前调用。load方法可以继承。调用子类的load方法之前,会先调用父类的load方法。一般我们不会手动去调用load方法,而是让系统去调用。

如果非要手动调用load方法,那么就会按照消息发送机制通过isa指针来寻找方法。

initialize

AnimalAnimal+OnePandaPanda+One 添加initialize方法。

基本使用

initialize类第一次接收到消息时,就会调用,相当于第一次使用类的时候就会调用initialize方法。

initialize方法的调用是通过消息发送机制调用的,不像load方法是直接通过函数指针去调用。

来验证一下调用顺序:

1
2
Animal(One): initialize
Panda (One): initialize

调用子类的initialize之前,会先保证调用父类的initialize方法。如果之前已经调用过initialize,就不会再调用initialize方法了。

当分类重写initialize方法时会先调用分类的方法不再调用原类的方法。

initialize是通过消息发送机制调用的,消息发送机制通过isa指针找到对应的方法与实现,因此优先找到分类方法中的实现,会优先调用。

另外还有一点需要注意:如果子类没有实现initialize方法,那么会调用父类的initialize方法,这一点我们会通过源码去验证。

源码分析

在底层源码里面通过函数class_getInstanceMethod和函数class_getClassMethod来找到实例方法和类方法

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
Method class_getInstanceMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;

// This deliberately avoids +initialize because it historically did so.

// This implementation is a bit weird because it's the only place that
// wants a Method instead of an IMP.

Method meth;
meth = _cache_getMethod(cls, sel, _objc_msgForward_impcache);
if (meth == (Method)1) {
// Cache contains forward:: . Stop searching.
return nil;
} else if (meth) {
return meth;
}

// 搜索方法
lookUpImpOrForward(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);

meth = _cache_getMethod(cls, sel, _objc_msgForward_impcache);
if (meth == (Method)1) {
// Cache contains forward:: . Stop searching.
return nil;
} else if (meth) {
return meth;
}

return _class_getMethod(cls, sel);
}

↓↓↓

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

// 检查是否已经调用了+initialize方法,如果没有调用过initialize方法
if ((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized()) {
// 调用initialize方法
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
}

...
...
...
}

↓↓↓

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
void initializeNonMetaClass(Class cls)
{
ASSERT(!cls->isMetaClass());

Class supercls;
bool reallyInitialize = NO;

// 在调用initialize方法之前,需要先判断是否调用了父类的initialize方法
supercls = cls->superclass;
if (supercls && !supercls->isInitialized()) {
initializeNonMetaClass(supercls);
}

...
...
...

#if __OBJC2__
@try
#endif
{
// 开始调用initialize方法
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()) {
...
...
...
}

else if (cls->isInitialized()) {

return;
}

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

↓↓↓

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

最终来到了消息发送objc_msgSend函数,给类对象发送initialize消息,消息发送机制通过isa指针找到对应的方法与实现。

上面我们说到的如果子类没有实现initialize方法,那么当我们有多个类继承了父类的时候,父类的initialize方法有可能会调用多次:

1
[[Panda alloc] init];

Panda继承自Animal,只实现了Animal的initialize方法,那么当第一次调用子类的时候会输出如下:

1
2
Animal: initialize
Animal: initialize

我们发现父类的initialize方法调用了2次,通过上面的源码我们已经知道只有当类在第一次接收消息的时候才会被调用,也就是说每个类只会initialize一次,但是父类的initialize为什么会被多次调用呢?

通过底层的逻辑将上面的代码转化为伪代码:

1
2
3
4
5
6
7
8
9
10
11
// 调用子类Panda之前先判断父类的initialize
if (Panda没有调用initialize) {
if (Panda的父类Animal没有调用initialize) {
// 调用父类Animal的initialize
objc_msgSend(Animal类, SEL_initialize)
}
}

// 调用子类Panda的initialize
objc_msgSend(Panda类, SEL_initialize)

消息发送机制是通过isa指针寻找到各自的元类对象中的类方法initialize的实现,但是子类没有实现initialize方法,所以会通过super_class指针找到父类的实现,所有最后才会来到父类的initialize方法。

注意:虽然父类被多次调用initialize方法,但是父类也是只初始化了一次。

总结

loadinitialize的区别,以及它们在category重写的时候的调用的次序。

调用方式:load是根据函数地址直接调用,initialize是通过objc_msgSend消息发送调用

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

调用顺序:先调用本类的load方法,先编译那个类,就先调用load。在调用load之前会先调用父类的load方法。分类中load方法不会覆盖本类的load方法,先编译的分类优先调用load方法。initialize先初始化父类,之后再初始化子类。如果子类没有实现initialize,会调用父类的initialize(所以父类的+initialize可能会被调用多次),如果分类实现了initialize,就覆盖类本身的initialize方法。

CATALOG
  1. 1. load
    1. 1.1. 基本使用
    2. 1.2. 调用原理
    3. 1.3. 调用顺序
    4. 1.4. 总结
  2. 2. initialize
    1. 2.1. 基本使用
    2. 2.2. 源码分析
    3. 2.3. 总结