彻底搞懂+load和+initialize

一、+load

结论一:

  • +load 方法在 main 函数执行之前调用;

调用栈如图:


load方法通过dyld调用

具体推到和验证详见:iOS类加载流程(一):类加载流程的触发

结论二:

  • 分类和类分别处理,存储在两个全局表中,所有类的 +load 方法调用完毕之后再调用分类的 +load 方法;

源码如下:

// 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 方法的调用顺序取决于符号表中的顺序,也就是编译顺序;

如下:


load方法和macho

结论四:

  • 递归调用类的 +load 方法,因此父类会先于子类调用,分类最后调用;

结论五:

  • dyld 中并没有使用 objc_msgSend 来调用函数,而是直接执行,所以不存在方法查找和消息传递机制,所以子类未实现 +load 方法的情况下不会去调用父类的 +load

源码如下:

__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

结论六:实现了 +load 方法的类会被添加到 mach-O 的 __DATA,__objc_nlclslist 表中,调用 +load 方法之前,会通过该表取出类的静态数据,完成对类的初始化;

注意,这个表 MachOView 不会解析,不代表该表中没有内容。内容如下:

__objc_nlclslist

因为表中的数据是 8 字节,所以很容易猜到存储的是指针。另外,因为 __PAGEZERO 段的存在,所以先调整下 MachOView 中的显示方式:

__PAGEZERO

iOS 中是大小端,所以上述三个地址应该是:

0x0100009358
0x01000093A8
0x01000093F8

对应的地址中的数据如下:

__objc_data

而这三个类就是实现了 +load 方法的三个类;

另外,从前面的代码中也可以看出来,先通过 _getObjc2NonlazyClassList_getObjc2NonlazyCategoryList 取出了类的静态数据和分类数据,再通过 realizeClassWithoutSwift 完成了类的初始化,最后才通过 call_load_methods 进行 +load 方法的调用;

这里调用 realizeClassWithoutSwift 只是一个兜底逻辑,其实非懒加载类在 map 时,已经被 objc 完成了初始化。load_image 主要的逻辑是通过非懒加载类的列表来遍历调用 +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. 
        ......
}

代码中有几个关键点:

    1. 初始化自己之前,递归执行父类的初始化操作;
    supercls = _class_getSuperclass(cls);
    if (supercls  &&  !_class_isInitialized(supercls)) {
        _class_initialize(supercls);
    }

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

    1. 初始化方法是通过 objc_msgSend 调用的,需要经过方法查找和消息转发的过程;
((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 方法,其特殊之处就在于第一次调用这个类的相关方法时被调用,相当于类对象/元对象的懒加载;

三、+initialize多次调用

关于 +initialize 被多次调用的场景:

  • 子类未实现 + initialize ,父类的+initialize 会被多次调用;

当第一次使用父类时,父类的 + initialize 被调用,当第一次使用子类时,因为子类无该方法,通过 objcMsgSend 传递机制,调用到父类的 + initialize,所以此时会被多次调用,因此, Apple 建议的做法是:

+ (void)initialize {
  if (self == [ClassName self]) {
    // ... do the initialization ...
  }
}

而 [Class self] 的源码如下:

+ (id)self {
    return (id)self;
}

该方法直接返回方法的调用者,也就是类对象。所以以上代码就是在排除掉子类调用 +initialize 方法的情况,确保只被初始化一次;

源码中的 _class_isInitialized 只是确保在调用父类 +initialize 方法之前,父类已经初始化完成了,而不是说父类初始化完成就不调用了;

四、总结

+load+initialize 方法本质都是做初始化的,只不过级别或者说针对的过程不一样。

非懒加载类在动态链接时已经被加载,而懒加载类在运行时被使用到时才加载。加载的过程就是对类的方法、属性、协议等进行装配,以完成类和元类的初始化操作。懒加载类如果实现了 +load 方法,那么该类就被添加到了非懒加载表,在 objc 的 map_image 流程中已经初始化完成了。只不过在后续的 load_image 流程中才触发 +load 方法的调用。这些都发生 dyld 流程中,在 main 函数之前。

+initialize方法针对的是 main 方法之后,而且是懒加载,使用到时才初始化。鉴于 objc_msgSend 的机制,存在多次调用的可能,但是可以使用代码进行判断。

总结:

  1. 目的不同:+load 方法用于添加类在加载完成后的一些逻辑,initialize 方法用于在类第一次被使用到时进行一些初始化操作;
  2. 时机不同:一个处于 dyld 流程中,位于 main 函数之前,一个处于 runloop 开启之后,位于main 函数之后;
  3. 机制不同:+load 方法的调用是根据类继承关系进行符号调用,所以子类如果未实现则不会调用父类的 +load 方法。而 initialize 是通过消息转发机制调用,子类未实现则进入父类的方法查找逻辑最终调用,所以需要添加一些判断来阻断该逻辑。

你可能感兴趣的:(彻底搞懂+load和+initialize)