OC中load方法的调用顺序(源码解析)

从objc苹果的源码中可以看到如下代码:

load_images(const char *path __unused, const struct mach_header *mh)
{
    if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
        didInitialAttachCategories = true;
        loadAllCategories();
    }

    // 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);
        // 准备调用类以及父类中的load方法
        prepare_load_methods((const headerType *)mh);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    // 真正调用load方法
    call_load_methods();
}

在runtime加载类和分类的load方法时候,会调用load_image方法,在load_images中会调用prepare_load_methods和call_load_methods,prepare_load_methods源码如下:

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

    runtimeLock.assertLocked();
    // 获取实现load方法的类的数组
    classref_t const *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    // 遍历load方法类的数组
    for (i = 0; i < count; i++) {
        // 存储当前类的父类和当前类load方法
        schedule_class_load(remapClass(classlist[i]));
    }
    
    // 获取实现了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);
    }
}

在prepare_load_methods方法中,会通过_getObjc2NonlazyClassList获得实现load方法类的数组,然后遍历这个数组通过schedule_class_load获得当前类的父类和当前类的load方法,然后通过_getObjc2NonlazyCategoryList方法获得实现了load方法的分类数组,遍历该数组,保存数据到loadable_categories

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

    // Ensure superclass-first ordering
    // 递归调用确保父类初始化
    schedule_class_load(cls->superclass);
    // 把当前类的相关信息添加到数组中
    add_class_to_loadable_list(cls);
    // 设置该类已被初始化
    cls->setInfo(RW_LOADED); 
}

schedule_class_load会被递归调用,判断该类未被初始化之后,优先调用父类,然后再保存当前类相关信息,并将该类设置为已经初始化

add_class_to_loadable_list源码如下:

void add_class_to_loadable_list(Class cls)
{
    IMP method;

    loadMethodLock.assertLocked();
    // 获取当前类load方法的地址
    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));
    }
    
    // 把clshe和method保存到loadable_classes的结构体数组中
    loadable_classes[loadable_classes_used].cls = cls;
    loadable_classes[loadable_classes_used].method = method;
    loadable_classes_used++;
}

把数据保存到loadable_classes中,并用loadable_classes_used计数,在准备方法调用完成之后,call_load_methods源码如下:

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

会通过do-while的遍历,让之前保存的loadable_classes_usd>0或者more_categories为真,会通过while遍历类的load方法,再调用分类的load

call_class_loads源码如下:

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]
", cls->nameForLogging());
        }
        (*load_method)(cls, @selector(load));
    }
    
    // Destroy the detached list.
    if (classes) free(classes);
}

load方法的调用是通过地址直接调用的

总结:

load方法会在runtime加载类、分类时调用,每个类、分类的+load,在程序运行过程中只调用一次

调用顺序:

先调用类的load,按照编译先后顺序调用,调用子类的+load之前会先调用父类的load

再调用分类的load,也是按照先编译、先调用的规则

原文地址:https://www.cnblogs.com/muzichenyu/p/14071246.html