问题: 简述一下load的调用时机以及调用顺序.


注: 本问题回答基于 objc4-818.2 版本.

load 方法是runtime在加载类、分类时自动调用的方法.

关于 load 方法的调用顺序主要有以下三点.

  1. 两个类没有继承关系或者是 所有的Category,那么load的调用顺序与 Compile Sources 中得编译顺序一致. 其可以通过 prepare_load_methods 方法可知.
void prepare_load_methods (const headerType *mhdr) {
    size_t count, i;

    runtimeLock.assertLocked();

    // 这一步获取顺序由 `Compile Source`的编译顺序确定.
    classref_t const *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);

    for (i = 0; i < count; i++) {
        // 内部会对父类的load方法进行添加加载.
        schedule_class_load(remapClass(classlist[i]));
    }

    // 这一步获取顺序由 `Compile Source`的编译顺序确定.
    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);
}
  1. 两个类如果存在继承关系,那么首先会调用父类的load方法,这可以通过 schedule_class_load相关源码可知.
static schedule_class_load(Class cls) 
{
    if (cls->info & CLS_LOADED) return;
    // 如果存在superclass指针,那么会先添加父类的load方法.
    if (cls->superclass) schedule_class_load(cls->superclass);
    add_class_to_loadable_list(cls);
    cls->info |= CLS_LOADED;
}
  1. load方法调用顺序是先调用类的load方法, 再调用Category的load方法.这一点可以通过查看源码中的 call_load_methods 方法来确认.
void call_load_methods(void) {
    static bool loading = NO;
    bool more_categories;

    loadMethodLock.assertLocked();
    if (loading) return;
    loading = YES;

    void *pool = objc_autoreleasePoolPush();

    do {
        // 调用类的 load 方法
        while (loadable_classes_used > 0) {
            call_class_loads();
        }

        // 调用分类的load方法
        more_categories = call_category_loads();

    } while (loadable_classes_used > 0 || more_categories);

    objc_autoreleasePoolPop(pool);

    loading = NO;
}

load方法的调用方式是直接通过指针寻找,并非常规的方法调用方式(消息发送机制).所以不存在方法覆盖问题.这一点可以通过源码中 call_class_loadscall_category_loads 这两个方法进行确认.

static void call_class_loads(void) {
    int i;
    
    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];
        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_method)(cls, @selector(load));
    }

    if (classes) free(classes);
}
static void 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()) {
            if (PrintLoading) {
                _objc_inform("LOAD: +[%s(%s) load]\n", 
                             cls->nameForLogging(), 
                             _category_getName(cat));
            }
            (*load_method)(cls, @selector(load));
            cats[i].cat = nil;
        }
    }

    // Compact detached list (order-preserving)
    shift = 0;
    for (i = 0; i < used; i++) {
        if (cats[i].cat) {
            cats[i-shift] = cats[i];
        } else {
            shift++;
        }
    }
    used -= shift;

    // Copy any new +load candidates from the new list to the detached list.
    new_categories_added = (loadable_categories_used > 0);
    for (i = 0; i < loadable_categories_used; i++) {
        if (used == allocated) {
            allocated = allocated*2 + 16;
            cats = (struct loadable_category *)
                realloc(cats, allocated *
                                  sizeof(struct loadable_category));
        }
        cats[used++] = loadable_categories[i];
    }

    // Destroy the new list.
    if (loadable_categories) free(loadable_categories);

    // Reattach the (now augmented) detached list. 
    // But if there's nothing left to load, destroy the list.
    if (used) {
        loadable_categories = cats;
        loadable_categories_used = used;
        loadable_categories_allocated = allocated;
    } else {
        if (cats) free(cats);
        loadable_categories = nil;
        loadable_categories_used = 0;
        loadable_categories_allocated = 0;
    }

    if (PrintLoading) {
        if (loadable_categories_used != 0) {
            _objc_inform("LOAD: %d categories still waiting for +load\n",
                         loadable_categories_used);
        }
    }

    return new_categories_added;
}


问题: 简述一下 initialize 的调用时机以及内部原理.


initialize 调用时机是类第一次调用方法时调用的方法.

initialize 调用方式是 objc_msgSend,所以方法调用顺序以及调用过程按照 objc_msgSend 的方式来即可.

initialize 的调用顺序是先调用父类的 initialize 再调用自身的 initialize. 调用顺序可以查看源码中得 initializeNonMetaClass 方法.

核心代码如下所示.

void  initializeNonMetaClass(Class cls) {

    ....

    Class supercls;
    bool reallyInitialize = NO;
    
    supercls = cls.getSuperclass();

    if (supercls && !supercls->isInitialized()) {
        // 如果父类存在,并且没有没有调用过initialize,那么先调用父类的方法.
        initializeNonMetaClass(supercls);
    }

    ...

}

IT界无底坑洞栋主 欢迎加Q骚扰:676758285