24·iOS 面试题·+(void) load; +(void) initialize; 有什么用处?
0

前言

写在最前面,Objective-C +load vs +initialize 这篇博客已经非常完美的回答了这道题目,我这篇文章就是学习完之后的学习笔记。强烈建议大家去阅读 Objective-C +load vs +initialize 。

+load 方法

+load 方法调用时机

+load 方法是当类被添加 Objective-C Runtime 时调用的,类调用顺序如下:

  1. 父类的 +load 方法
  2. 子类的 +load 方法
  3. 分类的 +load 方法(有多个分类,调用顺序是看分类文件的编译顺序)

+load 方法被调用的方式

下面是 runtime 中,调用 +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 = 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]\n", cls->nameForLogging());
        }
      //核心调用方式
        (*load_method)(cls, SEL_load);
    }

    // Destroy the detached list.
    if (classes) _free_internal(classes);
}

通过源码可以看到,调用方式是 (*load_method)(cls, SEL_load); ,这里是直接使用函数内存地址方式调用,而不是用 objc_masSend 发消息的机制;

直接利用函数地址调用的方法,没有走消息转发机制,就会出现这样子的现象:父类、子类、分类的 +load 互不影响,假设子类没有实现 +load 方法,也不会去调用多一次父类的 +load 方法。

+load 方法可以做什么

由于 +load 方法是在 Runtime 加载类时调用的,执行时机是比较早的,在这里我们可以做 Method Swizzling,对一些函数进行 Hook。(无痕埋点、AOP 都可以利用这个机制来实现)

+initialize 方法

+initialize 方法调用时机

+initialize 方法是在类或它的子类收到第一条消息之前被调用的,runtime 源码如下:

IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
                       bool initialize, bool cache, bool resolver)
{
    ...
        rwlock_unlock_write(&runtimeLock);
    }

    if (initialize  &&  !cls->isInitialized()) {
        _class_initialize (_class_getNonMetaClass(cls, inst));
        // If sel == initialize, _class_initialize will send +initialize and 
        // then the messenger will send +initialize again after this 
        // procedure finishes. Of course, if this is not being called 
        // from the messenger then it won't happen. 2778172
    }

    // The lock is held to make method-lookup + cache-fill atomic 
    // with respect to method addition. Otherwise, a category could 
    ...
}

当给一个类发送消息的时候,runtime 会调用 lookUpImpOrForward 函数,这里会对没有初始过的类进行初始化,调用 _class_initialize 函数。

+initialize 方法被调用的方式

下面是 runtime 中,调用 +initialize 方法最核心的源码:

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

    // Make sure super is done initializing BEFORE beginning to initialize cls.
    // See note about deadlock above.
    supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) {
        _class_initialize(supercls);
    }

    // Try to atomically set CLS_INITIALIZING.
    monitor_enter(&classInitLock);
    if (!cls->isInitialized() && !cls->isInitializing()) {
        cls->setInitializing();
        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]",
                         cls->nameForLogging());
        }

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

        if (PrintInitializing) {
            _objc_inform("INITIALIZE: finished +[%s initialize]",
    ...
}

通过这个函数,我们可以知道两点:

  1. 递归调用 +initialize 方法的,父类比子类先初始化
  2. 利用 objc_msgSend 发消息模式调用 +initialize 方法的(这里与其它普通方法调用一致)

故我们可以知道,如果子类没有实现 +initialize 方法,那么继承自父类的实现会被调用;如果一个类的分类实现了 +initialize 方法,那么就会对这个类中的实现造成覆盖。

+initialize 方法可以做什么

+initialize 方法调用的时机也是非常早的,并且是懒加载模式,可以在 +initialize 方法里面做一些初始化的工作。

+load 方法 和 +initialize 方法比较

这里直接用 Objective-C +load vs +initialize 中的总结:

+load +initialize
调用时机 被添加到 runtime 时 收到第一条消息前,可能永远不调用
调用顺序 父类->子类->分类 父类->子类
调用次数 1次 多次
是否需要显式调用父类实现
是否沿用父类的实现
分类中的实现 类和分类都执行 覆盖类中的方法,只执行分类的实现

总结

通过这个面试题,在网上找到了这篇博客:Objective-C +load vs +initialize ,又学习到知识点了,开心。

参考文献

Objective-C +load vs +initialize

01·iOS 面试题·项目中用过 Runtime 吗?

03·iOS 面试题·main()之前的过程有哪些?

06·iOS 面试题·Category 中有 load 方法吗?load 方法是什么时候调用的?load 方法能继承吗?

学为好人。

讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

  • 请注意单词拼写,以及中英文排版,参考此页
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`, 更多语法请见这里 Markdown 语法
  • 支持表情,使用方法请见 Emoji 自动补全来咯,可用的 Emoji 请见 :metal: :point_right: Emoji 列表 :star: :sparkles:
  • 上传图片, 支持拖拽和剪切板黏贴上传, 格式限制 - jpg, png, gif
  • 发布框支持本地存储功能,会在内容变更时保存,「提交」按钮点击时清空
  请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!