美文网首页
【iOS重学】一篇文章讲清楚+load和+initialize

【iOS重学】一篇文章讲清楚+load和+initialize

作者: 重庆妹子在霾都 | 来源:发表于2022-11-27 18:49 被阅读0次

    写在前面

    本文主要从底层源码上来分析一下+load+initialize方法的调用顺序以及它们之间的区别。

    +load

    +load方法会在Runtime加载类、分类的时候调用,每个类、分类的+load方法在程序运行过程中只会调用一次。

    +load的基本使用

    // Person 类
    @interface Person : NSObject
    
    @end
    
    @implementation Person
    
    + (void)load {
        NSLog(@"%s", __func__);
    }
    
    @end
      
    // Person + Test1 类
    @interface Person (Test1)
    
    @end 
    
    @implementation Person (Test1)
    
    + (void)load {
        NSLog(@"%s", __func__);
    }
    
    @end
    
    // Person + Test2 类
    @interface Person (Test2)
    
    @end
      
    @implementation Person (Test2)
    
    + (void)load {
        NSLog(@"%s", __func__);
    }
    
    @end
    
    // Student 类 继承自Person
    @interface Student : Person
    
    @end
      
    @implementation Student
    
    + (void)load {
        NSLog(@"%s", __func__);
    }
    
    @end
    
    // Student + Test1 类
    @interface Student (Test1)
    
    @end
      
    @implementation Student (Test1)
    
    + (void)load {
        NSLog(@"%s", __func__);
    }
    
    @end
    
    // Student + Test2 类
    @interface Student (Test2)
    
    @end
    
    @implementation Student (Test2)
    
    + (void)load {
        NSLog(@"%s", __func__);
    }
    
    @end
    

    上面场景,+load方法的打印顺序为:

    2022-11-28 09:50:34.392232+0800 CategoryDemo[88835:2251283] +[Person load]
    2022-11-28 09:50:34.392867+0800 CategoryDemo[88835:2251283] +[Student load]
    2022-11-28 09:50:34.392964+0800 CategoryDemo[88835:2251283] +[Animal load]
    2022-11-28 09:50:34.393013+0800 CategoryDemo[88835:2251283] +[Student(Test1) load]
    2022-11-28 09:50:34.393056+0800 CategoryDemo[88835:2251283] +[Person(Test1) load]
    2022-11-28 09:50:34.393107+0800 CategoryDemo[88835:2251283] +[Student(Test2) load]
    2022-11-28 09:50:34.393151+0800 CategoryDemo[88835:2251283] +[Person(Test2) load]
    

    那么,它们之间究竟是什么样的一个加载顺序呢?

    +load的底层源码

    +load方法的源码查看顺序:

    // objc-os文件
    1. _objc_init
    2. load_images
    
    // objc-runtime-new文件
    3. prepare_load_methods
      3.1 schedule_class_load
      3.2 add_category_to_loadable_list
    4. call_load_methods
      4.1 call_class_loads - (*load_method)(cls, @selector(load))
      4.2 call_category_loads
    

    【iOS重学】Category的底层原理中博主提到Runtime入口就是:objc-os文件中的_objc_init方法,我们就从这里入手分析一下底层源码。

    1.png

    解释
    1、先按照编译顺序将所有的类add_class_to_loadable_list装载到loadable_classes的数组中。
    2、再按照编译顺序将所有的分类add_class_to_loadable_list装载到loadable_classes的数组中。

    schedule_class_load方法源码如下:

    2.png

    解释
    在装载类到loadable_classes数组中时,如果存在父类,先将父类装载到loadable_classes中,再将类加载到数组中。

    call_load_methods方法源码如下:

    3.png

    解释
    在调用+load方法时,先调用类的+load方法再调用分类的+load方法。

    +load的调用顺序总结

    1、先调用类的+load
    1.1 按照编译顺序进行调用(先编译 -> 先调用)
    1.2 调用子类+load之前会先调用父类的+load

    2、再调用分类的+load
    2.1 按照编译顺序进行调用(先编译 -> 先调用)

    注意+load只会调用一次,比如StudentPerson之前编译,会先调用Person+load方法,表示Person已经被装载进内存了,所以+load不会被调用多次。

    +load的调用方式

    4.png
    struct loadable_class {
        Class cls;  // may be nil
        IMP method; // 这个method 就是+load的IMP 这个loadable_class就是用来加载类的结构体
    };
    

    如上图call_class_loads方法所示,+load方法的调用方式是:直接根据+load方法的函数地址直接去调用。

    +initialize

    +initialize方法会在类第一次接收到消息的时候调用。

    +initialize的基本使用

    // Person 类
    @interface Person : NSObject
    
    @end
    
    @implementation Person
    
    + (void)initialize {
        NSLog(@"%s", __func__);
    }
    
    @end
      
    // Person + Test1 类
    @interface Person (Test1)
    
    @end 
    
    @implementation Person (Test1)
    
    + (void)initialize {
        NSLog(@"%s", __func__);
    }
    
    @end
    
    // Person + Test2 类
    @interface Person (Test2)
    
    @end
      
    @implementation Person (Test2)
    
    + (void)initialize {
        NSLog(@"%s", __func__);
    }
    
    @end
    
    // Student 类 继承自Person
    @interface Student : Person
    
    @end
      
    @implementation Student
    
    + (void)initialize {
        NSLog(@"%s", __func__);
    }
    
    @end
    
    // Student + Test1 类
    @interface Student (Test1)
    
    @end
      
    @implementation Student (Test1)
    
    + (void)initialize {
        NSLog(@"%s", __func__);
    }
    
    @end
    
    // Student + Test2 类
    @interface Student (Test2)
    
    @end
    
    @implementation Student (Test2)
    
    + (void)initialize {
        NSLog(@"%s", __func__);
    }
    
    @end
    

    上面场景,+initialize方法的打印顺序为:

    2022-11-28 18:00:10.526685+0800 CategoryDemo[57742:2613672] +[Person(Test2) initialize]
    2022-11-28 18:00:10.527249+0800 CategoryDemo[57742:2613672] +[Student(Test2) initialize]
    

    +initialize的底层源码

    +initialize方法源码的查看顺序:

    // objc-runtime-new.mm文件
    1. class_getInstanceMethod
    2. lookUpImpOrForward
    3. realizeAndInitializeIfNeeded_locked
    4. initializeAndLeaveLocked
    5. initializeAndMaybeRelock
    6. initializeNonMetaClass
    7. callInitialize
    

    initializeNonMetaClass方法源码如下:

    5.png
    6.png

    解释
    从上面的源码我们大概可以看到:在调用callInitialize方法之前会去检查是否存在父类和父类是否被初始化,会先去调用父类的+initialize方法。

    +initialize的调用顺序总结

    先调用父类的+initialize,再调用子类的+initialize
    注意
    1、先初始化父类再初始化子类,每个类只会被初始化一次,但是可能会被调用多次。
    比如下面场景:
    1.Student没有实现+initialize方法,调用[Person alloc] [Student alloc]会调用两次Person+initialize方法。
    打印结果如下:

    2022-11-28 18:01:47.579047+0800 CategoryDemo[57804:2615728] +[Person(Test2) initialize]
    2022-11-28 18:01:47.579702+0800 CategoryDemo[57804:2615728] +[Person(Test2) initialize]
    

    2.Student实现了+initialize方法,Person调用过了+initialize,那么就不会再调用了。

    +initialize调用方式

    void callInitialize(Class cls)
    {
        ((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
        asm("");
    }
    

    如上图callInitialize方法所示,+initialize方法的调用方式是:obj_msgSend

    +load和+initialize对比

    1、调用时机:
    +load是在Runtime加载类、分类的时候调用(只会调用一次),在main函数之前。
    +initialize是在类第一次接收到消息的时候调用,只会初始化一次(父类的+initialize可能会被调用多次),在main函数之后。

    2、调用方式:
    +load是根据函数地址直接调用。
    +initialize是通过objc_msgSend调用。

    相关文章

      网友评论

          本文标题:【iOS重学】一篇文章讲清楚+load和+initialize

          本文链接:https://www.haomeiwen.com/subject/redkfdtx.html