前言
在iOS
开发中,我们经常会听到load
方法,那么到底什么是load
方法,它什么时候调用?
首先,我们先来看看官方给出的说明:
Discussion
The load message is sent to classes and categories that are both dynamically loaded and statically linked, but only if the newly loaded class or category implements a method that can respond.
The order of initialization is as follows:
1.All initializers in any framework you link to.
2.All +load methods in your image.(这个image指的就是二进制可执行文件)
3.All C++ static initializers and C/C++ __attribute__(constructor) functions in your image.
4.All initializers in frameworks that link to you.
In addition:
1.A class’s +load method is called after all of its superclasses’ +load methods.
2.A category +load method is called after the class’s own +load method.
In a custom implementation of load you can therefore safely message other unrelated classes from the same image, but any load methods implemented by those classes may not have run yet.
大致意思是:发送给类或者分类的load
消息是同时动态加载和静态链接的,但是只有新加载的类或者分类实现了一个方法才能响应。初始化的顺序如下:
- 链接到的任何框架中的所有初始化程序
- 镜像文件中所有实现了
load
方法 - 镜像文件中所有的
C++
的静态初始化和C/C++
构造器函数 - 链接到您的框架中的所有初始化程序
另外: - 所有的
superclass
的load
执行完以后才会执行该子类的load
- 类中的
load
方法是先于category
中的执行的
准备工作
下面我们实现一段代码来验证以下:
先一个TPerson
的类:
@interface TPerson : NSObject
@property (nonatomic, copy) NSString *name;
- (void)sayHello;
+ (void)sayYo;
@end
#import "TPerson.h"
@implementation TPerson
+ (void)load {
NSLog(@"类-load");
}
- (void)sayHello {
NSLog(@"%s",__func__);
}
+ (void)sayYo {
NSLog(@"%s",__func__);
}
@end
然后再实现一个分类:
#import "TPerson.h"
@interface TPerson (addition)
- (void)cate_instanceMethod;
+ (void)cate_classMethod;
@end
@implementation TPerson (addition)
+ (void)load {
NSLog(@"分类-load");
}
- (void)cate_instanceMethod {
NSLog(@"%s",__func__);
}
+ (void)cate_classMethod {
NSLog(@"%s",__func__);
}
@end
运行程序,我们发现在main函数之前,类和分类中的load
方法就调用了。那么load
方法到底具体是在哪里被调用的呢?
load_images
在 分类的加载中,我们知道,实现了load
方法的类就是非懒加载类,而非懒加载类的一些相关处理在load_images
方法中。
// Process +load in the given images which are being mapped in by dyld.
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();
}
从注释来看,load_images
方法是处理被dyld
映射给定的镜像文件中的load
方法,也就是说实现了load
方法类会在这里被处理。而方法中的第一个判断条件也是必须实现load
方法。然后,会进入prepare_load_methods
这个方法,这个方法是用来做准备工作的:
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);
......
realizeClassWithoutSwift(cls);
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);
}
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 (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++;
}
struct loadable_class {
Class cls; // may be nil
IMP method;
};
由代码可知,对非懒加载类的处理是将其放入loadable_classes
中,每个元素是一个名为loadable_class
结构体,其中包含了类和load
方法。
处理非懒加载分类
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 (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++;
}
struct loadable_category {
Category cat; // may be nil
IMP method;
};
对非懒加载分类的处理是将其放入loadable_categories
中,每个元素是一个名为loadable_category
结构体,其中包含了分类和load
方法。
通过以上处理,就把类和分类分别放入loadable_classes
和loadable_categories
这两个list
中,这样准备工作完成了。下面就该调用了:
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;
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);
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;
(*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()) {
(*load_method)(cls, SEL_load);
cats[i].cat = nil;
}
}
......
return new_categories_added;
}
在这个方法中,然后对这两个list
进行遍历,由于我们在存储的时候已经存储了method
,所以可以直接使用IMP
进行调用load
方法。在类的列表中,由于结构体的一个元素就是class
,另一个就是load
的method
,所以使用(*load_method)(cls, SEL_load)
就是直接调用类的load
的方法。而在分类中,就是通过分类拿到类,然后再进行调用。
总结
load
方法是先于main
函数、在load_images
中调用。首先,会把所有非懒加载类的load
方法放到loadable_classes
中,把分类的load
方法loadable_categories
中,然后对其进行遍历,找到相关的类和方法直接使用存储的load method
调用。
Tips
主类和分类有相同的方法,调用顺序是什么?
- 如果是
load
方法,由于call_class_loads()
先于call_category_loads()
,所以是先调用主类,然后调用分类。 - 如果是其他方法,则只会调用分类的方法,不会调用主类的方法。因为分类方法都是通过
attachList
方法加入到类的ro
或者rw
中,而attachList
方法是先将原来的列表进行扩容,然后将旧数据移动到列表的尾部位置,再把新的数据copy
到列表头部位置。所以分类的方法在前面,主类方法在后面,方法查找的时候找到前面的方法就直接返回了,并不会再去调用主类的方法。详见类的加载-列表绑定。
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
else {
// 1 list -> many lists
List* oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
网友评论