iOS原理探究底层面试题(重要)

彻底搞懂+load和+initialize

2020-07-16  本文已影响0人  康小曹

一、+load

直接过结论并且验证:

调用栈如图:


load方法通过dyld调用

源码如下:

// List of classes that need +load called (pending superclass +load)
// This list always has superclasses first because of the way it is constructed
static struct loadable_class *loadable_classes NOBSS = NULL;

// List of categories that need +load called (pending parent class +load)
static struct loadable_category *loadable_categories NOBSS = NULL;

__private_extern__ void prepare_load_methods(header_info *hi)
{
    size_t count, i;

    rwlock_assert_writing(&runtimeLock);

    class_t **classlist = 
        _getObjc2NonlazyClassList(hi, &count);
    for (i = 0; i < count; i++) {
        class_t *cls = remapClass(classlist[i]);
        // 内部会调用add_class_to_loadable_list添加到loadable_classes数组中
        schedule_class_load(cls);
    }

    category_t **categorylist = _getObjc2NonlazyCategoryList(hi, &count);
    for (i = 0; i < count; i++) {
        category_t *cat = categorylist[i];
        // Do NOT use cat->cls! It may have been remapped.
        class_t *cls = remapClass(cat->cls);
        realizeClass(cls);
        assert(isRealized(cls->isa));
       // 这个方法中会存入 loadable_categories
        add_category_to_loadable_list((Category)cat);
    }
}

将类添加到数组的代码如下,分类的就不列出了:

static void schedule_class_load(class_t *cls)
{
    assert(isRealized(cls));  // _read_images should realize

    if (cls->data->flags & RW_LOADED) return;

    class_t *supercls = getSuperclass(cls);
    if (supercls) schedule_class_load(supercls);

    add_class_to_loadable_list((Class)cls);
    changeInfo(cls, RW_LOADED, 0); 
}

dyld 会分别将类和分类添加到两个全局数组中,以后循环取出这两个数组中的元素,递归执行 load。这两个数组也就是分类 load 不覆盖原来类的 load 方法的本质;

如下:


load方法和macho

源码如下:

__private_extern__ void call_load_methods(void)
{
    static BOOL loading = NO;
    BOOL more_categories;

    recursive_mutex_assert_locked(&loadMethodLock);

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

这里也可以看出,两个 while 循环,先通过 call_class_loads 执行所有类的 load 方法,再通过 call_category_loads 执行分类的 load 方法。

其中,执行类的 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 = NULL;
    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;
        IMP load_method = classes[i].method;
        if (!cls) continue; 

        if (PrintLoading) {
            _objc_inform("LOAD: +[%s load]\n", _class_getName(cls));
        }
        (*load_method) ((id) cls, SEL_load);
    }
    
    // Destroy the detached list.
    if (classes) _free_internal(classes);
}

从上面可以看到,直接取出类地址之后执行,并没有调用 objc_msgSend。因此,如果子类没有实现 load 方法,并不会去调用父类的 load;

二、+initalize

先看一眼源码:

__private_extern__ void _class_initialize(Class cls)
{
    Class supercls;
    BOOL reallyInitialize = NO;

    // Get the real class from the metaclass. The superclass chain 
    // hangs off the real class only.
    cls = _class_getNonMetaClass(cls);

    // Make sure super is done initializing BEFORE beginning to initialize cls.
    // See note about deadlock above.
    supercls = _class_getSuperclass(cls);
    if (supercls  &&  !_class_isInitialized(supercls)) {
        _class_initialize(supercls);
    }
    
    // Try to atomically set CLS_INITIALIZING.
    monitor_enter(&classInitLock);
    if (!_class_isInitialized(cls) && !_class_isInitializing(cls)) {
        _class_setInitializing(cls);
        reallyInitialize = YES;
    }
    monitor_exit(&classInitLock);
    
    if (reallyInitialize) {
        // We successfully set the CLS_INITIALIZING bit. Initialize the class.
        
        // Record that we're initializing this class so we can message it.
        _setThisThreadIsInitializingClass(cls);
        
        // Send the +initialize message.
        // Note that +initialize is sent to the superclass (again) if 
        // this class doesn't implement +initialize. 2157218
        if (PrintInitializing) {
            _objc_inform("INITIALIZE: calling +[%s initialize]",
                         _class_getName(cls));
        }

        ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);

        if (PrintInitializing) {
            _objc_inform("INITIALIZE: finished +[%s initialize]",
                         _class_getName(cls));
        }        
        
        // Done initializing. 
        ......
}

代码中有几个关键点:

    supercls = _class_getSuperclass(cls);
    if (supercls  &&  !_class_isInitialized(supercls)) {
        _class_initialize(supercls);
    }

这里很明显是先判断父类有没有初始化完成,如果没有则递归执行父类的初始化;

((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);

因此,可以做出几个结论:

  1. initialize 方法是在这个类第一次被使用到时才调用,具体为第一次调用该类的相关方法;
  2. 父类的 initialize 先执行;
  3. 如果子类没有实现 initialize,则会调用父类的initialize;
  4. 如果子类实现了 initialize,那么就直接执行子类的 initialize
  5. 理论上只会调用一次,但是因为采用了 objc_msgSend 来调用,所以如果子类没有实现 initialize,那么就会多次调用父类的 initialize,可以通过添加 if (self == [ClassName self]) 来进行判断;
  6. 不像 load 方法,会区分类和分类保存在两个数组中分别执行, 分类的 initialize 方法会覆盖原来类的 initialize,且遵循分类的编译顺序原则,最靠后的分类最终替换掉之前的 initialize 方法;

说白了,initialize 就是一个普通 OC 方法,其特殊之处就在于第一次调用这个类的相关方法时被调用,相当于类对象/元对象的懒加载;

总结

load 和 initialize 方法本质都是做初始化的,只不过级别或者说针对的过程不一样。load 只会调用一次,在 main 方法调用之前做初始化,比如方法交换。initialize 方法针对的是 main 方法之后,而且是懒加载,使用到时才初始化。鉴于 objc_msgSend 的机制,存在多次调用的可能,但是可以使用代码进行判断。

  1. 两者的调用机制完全不同,一个是使用 dyld 调用,一个是走 objc_msgSend 机制;
  2. load 调用先执行类的调用,再执行分类的调用,递归调用,父类的 load 方法先于子类执行。但子类和父类无关联,只有实现了 load 方法才调用,未实现也不调用父类的load 方法;
  3. initialize 因为是走 objc 的消息寻找和转发机制,所以分类会覆盖原类的 initialize 方法。initialize 在类第一次被使用到时调用,虽然也是循环调用,但是子类未实现时需要调用父类的 initialize 方法来完成类的初始化操作;
上一篇下一篇

猜你喜欢

热点阅读