iOS-浅谈OC中+load和+initialize方法

2019-05-27  本文已影响0人  晴天ccc

目录

  • +load方法
    ---- 类和分类的+load方法调用顺序
    ---- 不同类间的+load方法调用顺序
    ---- 不同分类间的+load方法调用顺序
    ---- load总结
  • +initialize方法
    ---- +initialize方法调用时机
    ---- +initialize用途
    ---- +initialize和+load对比
    ------------ 调用时机
    ------------ 调用顺序
    ------------ 调用方式

+load方法

分析源码:

  • _objc_init(运行时初始化方法)
  • load_images(加载模块方法)
  • call_load_methods(调用load方法)
  • call_class_loads(调用类的load方法)
  • call_category_loads(调用分类的load方法)
static void call_class_loads(void)
{
    // 读取所有类
    struct loadable_class *classes = loadable_classes;
......
    // 遍历所有类
    for (i = 0; i < used; i++) {
        // 找到class对象中的+load方法指针
        load_method_t load_method = (load_method_t)classes[i].method;
        // 调用+load方法
        (*load_method)(cls, @selector(load));
    }
......
}
static bool call_category_loads(void)
{
    // 读取所有分类
    struct loadable_category *cats = loadable_categories;
......
    // 遍历所有分类
    for (i = 0; i < used; i++) {
        // 找到分类里+load方法指针
        load_method_t load_method = (load_method_t)cats[i].method;
        // 调用+load方法
        (*load_method)(cls, @selector(load));
    }
......
}

由源码观察可知,先调用了call_class_loads方法再调用了call_category_loads方法。
所以类和分类调用顺序是:
先调用所有类中的+load方法,再调用所有分类的+load方法。

分析运行时源码:

  • prepare_load_methods(预加载+load方法)
  • schedule_class_load(定制类的load方法)
static void schedule_class_load(Class cls)
{
    if (!cls) return;
    ASSERT(cls->isRealized());  // _read_images should realize

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

    // Ensure superclass-first ordering
    schedule_class_load(cls->getSuperclass());

    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED); 
}

通过分析源码可知:schedule_class_load方法内部有一个递归调用,每次传入参数是cls的superclass,将cls添加到loadable_classes数组中,所以最终结果导致在loadable_classes数组中父类class会排在子类class前面,再结合call_class_loads方法得出:

  • 1、先调用父类中的+load方法,再调用子类中的+load方法。
  • 2、没有继承关系的类,按照编译顺序调用+load方法。
void add_category_to_loadable_list(Category cat)
{
......
    loadable_categories[loadable_categories_used].cat = cat;
......
}

结合call_category_loads方法得出:
分类是按照编译顺序调用+load方法。

  • +load方法会在runtime加载类、分类时调用。
  • 每个类、分类的+load,在程序运行过程中只调用一次。
  • +load方法是根据方法地址直接调用,并不是经过objc_msgSend函数调用

调用顺序

  • 先调用类的+load
  • 按照编译先后顺序调用(先编译,先调用)
  • 调用子类的+load之前会先调用父类的+load
  • 再调用分类的+load
  • 按照编译先后顺序调用(先编译,先调用)

+initialize方法

查看objc_msgSend源码,因为是汇编语言理解起来困难,所以可以从消息发送的过程入手。
isa->找到类/元类对象->查找方法->调用

从查找方法部分入手

  • class_getInstanceMethod(查找对象方法)/class_getClassMethod(查找类方法)
  • lookUpImpOrForward(查找方法实现)
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
......    
    // 如果没有初始化,需要调用initialize方法
    if ((behavior & LOOKUP_INITIALIZE)  &&  !cls->isInitialized()) {
        initializeNonMetaClass (_class_getNonMetaClass(cls, inst));
    }
......
}

initializeNonMetaClass:

void initializeNonMetaClass(Class cls)
{
......
    // 拿到父类
    supercls = cls->getSuperclass();
    // 父类如果没有初始化,先初始化父类
    if (supercls  &&  !supercls->isInitialized()) {
        initializeNonMetaClass(supercls);
    }
......
    callInitialize(cls);
......
}

// 调用cls的initialize方法
void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
    asm("");
}

通过查看源码可知,在objc_msgSend(消息发送)过程中,会先调用类的+initialize方法。
观察到initializeNonMetaClass方法内部有递归调用,递归参数是superclass,所以调用子类+initialize前,会先判断父类是否已经初始化了,如果没有会先调用父类的+initialize方法。

总结:

  • 1、+initialize方法会在类第一次接收到消息时调用。
  • 2、调用顺序
    先调用父类的+initialize,再调用子类的+initialize(先初始化父类,再初始化子类,每个类只会初始化1次)

initialize用途就是在第一次使用这个类的时候做一些事情。

+load在runtime加载类、分类时调用(只会调用一次)
+initialize是类第一次接收到消息时调用,每一个类只会initialize一次(如果子类没有实现initialize方法,父类的initialize方法可能会被调用多次)

+load

  • 1、先调用类的+load
    先编译的类,优先调用+load
    调用子类+load之前,会先调用父类的+load
  • 2、再调用分类
    先编译的分类,优先调用+load

+initialize

  • 1、先初始化父类
  • 2、再初始化子类(可能最终调用的是父类的+initialize方法)

+load是通过方法指针调用,+initialize是通过objc_msgSend方式调用,所以+initialize会有以下特点:

  • 1、如果子类没有实现+initialize方法,会调用父类的+initialize(所以父类的+initialize可能会被调用多次,第一次为自己初始化时调用,其他为子类消息发送调用)
  • 2、如果分类实现了+initialize,会覆盖主类的+initialize。
上一篇下一篇

猜你喜欢

热点阅读