load

问题一:Category中有load方法吗?

答:有的,但是和其他方法不同,Category 中的load和类中的load方法,并不是简单的覆盖或者继承。用一个简单的工程可以看出:

LoadClass.m:

1
2
3
4
5
6
7
8
9
#import "LoadClass.h"

@implementation LoadClass

+(void)load{

NSLog(@"Class Load Execute");
}
@end

LoadClass+LoadClassCategory.m:

1
2
3
4
5
6
7
8
9

#import "LoadClass+LoadClassCategory.h"

@implementation LoadClass (LoadClassCategory)

+(void)load{
NSLog(@"Category Load Execute");
}
@end

输出:

1
2
2019-11-10 20:57:22.986038+0800 LoadDemo[88431:1086125] Class Load Execute
2019-11-10 20:57:22.986486+0800 LoadDemo[88431:1086125] Category Load Execute

从输出可以看出,并没有出现方法覆盖的情况,两个load方法都被执行了。

问题二:load方法是什么时候被调到的?

要探究这个问题其实很简单,只需要在上述代码中的load方法中下一个断点,查看调用堆栈即可:
image.png
程序运行并命中断点之后,查看输出:
image.png
这里我们主要看load的上层调用load_images。显然这个时候,我们需要借助runtime源码去分析。
首先找到这个函数:

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

/***********************************************************************
* load_images
* Process +load in the given images which are being mapped in by dyld.
*
* Locking: write-locks runtimeLock and loadMethodLock
**********************************************************************/
extern bool hasLoadMethods(const headerType *mhdr);
extern void prepare_load_methods(const headerType *mhdr);

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

函数内部其实很简单,就是几个函数的调用,这里,我们主要关注两个函数—–prepare_load_methodscall_load_methods
先看prepare_load_methods

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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 函数内部遍历所有类,并调用schedule_class_load 函数,将所有类中的load方法加入列表中,之后有遍历所有的category 并调用add_category_to_loadable_list 函数将category 中的load方法加入列表中。大致的流程就是这样子的,如果想更深入的研究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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/***********************************************************************
* call_load_methods
* Call all pending class and category +load methods.
* Class +load methods are called superclass-first.
* Category +load methods are not called until after the parent class's +load.
*
* This method must be RE-ENTRANT, because a +load could trigger
* more image mapping. In addition, the superclass-first ordering
* must be preserved in the face of re-entrant calls. Therefore,
* only the OUTERMOST call of this function will do anything, and
* that call will handle all loadable classes, even those generated
* while it was running.
*
* The sequence below preserves +load ordering in the face of
* image loading during a +load, and make sure that no
* +load method is forgotten because it was added during
* a +load call.
* Sequence:
* 1. Repeatedly call class +loads until there aren't any more
* 2. Call category +loads ONCE.
* 3. Run more +loads if:
* (a) there are more classes to load, OR
* (b) there are some potential category +loads that have
* still never been attempted.
* Category +loads are only run once to ensure "parent class first"
* ordering, even if a category +load triggers a new loadable class
* and a new loadable category attached to that class.
*
* Locking: loadMethodLock must be held by the caller
* All other locks must not be held.
**********************************************************************/
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方法的调用流程为:先调用类中的load方法,再调用类别中的load方法。这其实可以说明一个问题:类中的load方法比类别中的load方法先执行。
到这里,其实咱们就可以回答刚刚的那个问题了:
答:load方法是在类或者类别添加到runtime的时候,被调用的,而且其调用顺序是:先类中的load,再类别中的load