iOS 升级打怪 - load 与 initialize

1,808 阅读3分钟

load

该方法会在 runtime 加载类和分类时调用,可以通过自定义的实现来进行类的一些定制化操作。一般用来执行一些只会执行一次的代码。

官方文档可以看出 load 的调用顺序:

截屏2021-10-25 下午3.58.24.png

  • 先调用父类的 +load,再调用子类的。
  • 先调用类的 +load,再调用分类的。
  • 如果是不是继承关系的类,则是先编译先调用;同级分类的也是先编译先调用。

下面通过源码来证实一下上面的调用关系。源码版本为 objc4-818.2

因为 +load 会在 runtime 加载类和分类时调用,所以源码入口还是 _objc_init 方法。在该方法中会调用 load_images 方法。

  • load_images
void
load_images(const char *path __unused, const struct mach_header *mh)
{
    ......
    // Discover load methods,先找出所有的 load 方法
    {
        mutex_locker_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }

    // Call +load methods (without runtimeLock - re-entrant),再去加载 load 方法。
    call_load_methods();
}
  • prepare_load_methods:查找所有的类和分类的 load 方法。
void prepare_load_methods(const headerType *mhdr)
{
    ......
    // 查找类的 load 方法
    for (i = 0; i < count; i++) {
        schedule_class_load(remapClass(classlist[i]));
    }
    // 查找分类的 load 方法
    category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
    for (i = 0; i < count; i++) {
        ......
        add_category_to_loadable_list(cat);
    }
}
  • schedule_class_load:查找所有类的 load 方法。
static void schedule_class_load(Class cls)
{
    ......
    // Ensure superclass-first ordering,通过这个递归可以验证:先加载父类,再加载子类这一结论。
    schedule_class_load(cls->getSuperclass());
    add_class_to_loadable_list(cls);
    ......
}
  • call_load_methods
void call_load_methods(void)
{
    ......
    // 通过这个do-while 循环可以验证:先调用类的 load 方法,再调用分类的 load 方法这一结论。
    do {
        // 1. Repeatedly call class +loads until there aren't any more
        while (loadable_classes_used > 0) {
            // 调用类的 load
            call_class_loads();
        }

        // 2. Call category +loads ONCE,调用分类的 load
        more_categories = call_category_loads();

        // 3. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used > 0  ||  more_categories);
    ......
}
  • call_class_loads:调用类的 load 方法。
static void call_class_loads(void)
{
    ......
    // 通过方法地址直接调用
    (*load_method)(cls, @selector(load));
    ......
}
  • call_category_loads:调用分类的 load 方法。
static bool call_category_loads(void)
{
    ......
    // 通过方法地址直接调用
    (*load_method)(cls, @selector(load));
    ......
}

(*load_method)(cls, @selector(load)); 从这句代码我们也可以得出一个结论:+load 方法直接通过方法地址调用,而不是走的消息机制那套流程。

runtime 的 +load 调用流程图:

+ load 加载流程.png

initialize

通过官方文档可以得出下面几个结论:

  • 它在类第一次接受消息时调用且每个类只会调用一次。
  • 它是线程安全的。
  • 如果子类没有实现该方法,那父类可能会多次调用;如果分类实现了该方法,则只会调用分类的方法实现。
// 以下代码可以避免调用多次
+ (void)initialize { 
    if (self == [ClassName self]) { 
    // ... do the initialization ... 
    }
}

接着,还是通过源码来验证下上面的结论。

  • class_initialize
Class class_initialize(Class cls, id obj)
{
    runtimeLock.lock();
    return initializeAndMaybeRelock(cls, obj, runtimeLock, false);
}
  • initializeAndMaybeRelock
static Class initializeAndMaybeRelock(Class cls, id inst,
                                      mutex_t& lock, bool leaveLocked)
{
    ......
    // runtimeLock is now unlocked, for +initialize dispatch
    ASSERT(nonmeta->isRealized());
    initializeNonMetaClass(nonmeta);

    if (leaveLocked) runtimeLock.lock();
    return cls;
}
  • initializeNonMetaClass
void initializeNonMetaClass(Class cls)
{
    ......
    // Make sure super is done initializing BEFORE beginning to initialize cls.从下面的递归可以验证:+initialize 先调用父类再调用子类
    supercls = cls->getSuperclass();
    if (supercls  &&  !supercls->isInitialized()) {
        initializeNonMetaClass(supercls);
    }
    ......
    callInitialize(cls);
    ......
}
  • callInitialize
void callInitialize(Class cls)
{
    // objc_msgSend,从此可以验证:+initialize 是通过消息机制调用。
    ((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
    asm("");
}

+initialize 调用流程图:

+ initialize 加载流程.png

总结

  • 调用时机:+load 在 Runtime 加载类和分类时调用; +initialize 是类在第一次接受消息时调用;
  • 调用本质:+load 是通过方法地址直接调用;+initialize 则是通过消息机制调用。
  • 调用顺序:
    • +load 先调用类,再调用分类。若类有父类则先调用父类再调用子类。
    • +load 平级类则先编译先调用;平级分类也是如此。
    • +initialize 先调用父类再调用子类。若分类也实现则只会调用分类。
    • 若子类没有实现 +initialize ,则父类可能会调用多次。