load底层原理

331 阅读2分钟

load方法是在runtime时候调用的.

//objc-os.mm
_objc_init()
load_images()
prepare_load_methods()
call_load_methods()

call_load_methods实现如下

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

可见是所有的类的load调用完毕,才会调用分类

那么那么多类,调用顺序又是怎样的呢?

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; 
        (*load_method)(cls, SEL_load);
    }
    ...
}

可以看到是通过for遍历调用的,那么loadable_classes数组的顺序是怎样的,那么其调用顺序就是怎样的

根据上面的调用顺序call_load_methods前面有一个prepare_load_methods,

实现如下

void prepare_load_methods(const headerType *mhdr)
{
    size_t count, i;

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

_getObjc2NonlazyClassList获取的类的列表是和编译顺序是一样的.那么看schedule_class_load内部实现

static void schedule_class_load(Class cls)
{
    // Ensure superclass-first ordering
    schedule_class_load(cls->superclass);

    add_class_to_loadable_list(cls);
    ...
}

递归调用获取了父类

void add_class_to_loadable_list(Class cls)
{
    
    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++;
}

我们上面说了loadable_classes是怎么生成的,决定了类的load方法的调用顺序.

所以先传入schedule_class_load也会先被add_class_to_loadable_list添加,而添加一个类的时候,由于schedule_class_load先递归调用父类的,所以父类的又先于当前类被添加

而传入schedule_class_load的类是遍历从_getObjc2NonlazyClassList中获取的.而该方法取决于编译顺序,

所以:

  • 同层级的类,先编译的先调用
  • 父类先于子类调用

比如有Person,Student,Dog类,Student继承自Person,PeronDog是同级别,都继承自NSObject

  • Person的调用肯定在Student前面

  • Dog的调用顺序取决于其编译顺序

如果编译顺序是,Dog在最前面

Dog->Student->Person
Dog->Person->Student

那么调用顺序是

Dog->Person->Student

如果编译顺序是,Dog在最后面

Person->Student->Dog
Student->Person->Dog

那么调用顺序是

Person->Student->Dog

如果编译顺序是,Dog在中间

Person->Dog->Student  or
Student->Dog->Person

那么调用顺序是

Person->Dog->Student
Person->Student->Dog

给Person增加分类test1和test2,无疑,分类肯定是最后调用

2020-09-26 15:38:30.528745+0800 objc-test[5734:503102] Person load

2020-09-26 15:38:30.529252+0800 objc-test[5734:503102] Student load

2020-09-26 15:38:30.529321+0800 objc-test[5734:503102] Dog load

2020-09-26 15:38:30.529372+0800 objc-test[5734:503102] Person Test1

2020-09-26 15:38:30.529418+0800 objc-test[5734:503102] Person Test2

那么分类的调用顺序是怎样的呢?

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

可以看到也是遍历调用,那么就取决于loadable_categories的顺序,回到prepare_load_methods

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

_getObjc2NonlazyCategoryList取决于编译顺序

build phases中更换test1test2的编译顺序,load调用顺序随之改变

Dog也增加分类,test1test2,打印顺序仍旧同编译顺序一致