iOS 底层原理- load和initialize分析

51 阅读5分钟

1.+load()方法

load方法是我们平时开发中常用的,很多面试题也经常会出现,比如父类分类load方法的调用顺序等。这里我们首先通过案例明确一下load方法的调用顺序,然后结合源码去分析。

1.案例分析

引入一个案例,父类 JHSPerson、子类JHSTeacher及其分类均实现了load方法,见下面代码:

// 父类

@implementation JHSPerson : NSObject

+ (void)load{
    NSLog(@"%s", __func__);
}

@end

// 父类分类

@implementation JHSPerson (JHS)

+ (void)load{
    NSLog(@"%s", __func__);
}

@end

// 子类

@implementation JHSTeacher : JHSPerson

+ (void)load{
    NSLog(@"%s", __func__);
}

@end

// 子类分类

@implementation JHSTeacher (JHS)

+ (void)load{
    NSLog(@"%s", __func__);
}

运行程序,会发现,系统会自动调用各个类的load方法。调用顺序为:父类->子类->分类。见下图:

6.png

2.load方法的过程

那么上面案例中的load方法是什么时候调用的呢? 上一篇中我们知道,调用sNotifyObjCInit也就是调用了load_images,在libobjc.A.dylib源码中可以找到load_images实现。见下图:

7.png

load_images函数实现中,找到load方法的调用入口:call_load_methods。同时根据注释,我们也可以验证这一点。在call_load_methods 中会反复调用class+load方法,直到没有更多。

8.png

  1. 类load方法 在call_class_loads中实现了函数级别的load方法调用,见下图:

10.png

源码中是从loadable_classes获取获取的方法,并调用。

  1. 分类load方法 在call_category_loads中实现了函数级别的load方法调用,见下图:

9.png

源码中是从loadable_categories获取获取的方法,并调用。

这里需要思考个问题,我们找到了load方法的调用过程,但是这个顺序在哪里确定下来的?load_images中有个关键地方还没有研究!

3.load方法调用顺序的确定

Discover load methods发现加载方法,这里会对load方法进行处理,并放入对应的loadable_classesloadable_categories表中。

6.png prepare_load_methods方法到底在做什么准备工作呢?进去看看。见下面源码:

11.png

  1. 非懒加载的类处理 _getObjc2NonlazyClassList方法做什么呢?获取所有实现了+load方法的类(也就是非懒加载的类),加入到一个静态数组loadable_classes。看schedule_class_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); 

}

首先判断判断类是否为空,如果为空,直接返回;如果不为空,判断是否已经设置为RW_LOADED,如果已经被设置为RW_LOADED ,直接返回。然后进行了递归操作,获取当前类的父类,以确保父类优先进行了处理,然后调用add_class_to_loadable_list,将load方法添加到loadable_classes数组中。

  1. 非懒加载的分类处理 _getObjc2NonlazyCategoryList方法做什么呢?获取所有实现了+load的分类(非懒加载的分类),然后判断分类所对应的类是否为nil,如果分类所对应的类为nil则跳过,反之初始化分类所对应的类,然后将分类加入一个静态数组loadable_categories

总结: 走完prepare_load_methods,也就是准备工作做好后,程序走到call_load_methods,调用load方法call_load_methods方法的实现,从代码中我们可以看出来,在这个方法中系统会把类本身的+load方法分类的+load方法都调用了,并且类的+load要比分类中先调用。那么如果有多个分类都实现了+load,先调用哪个分类呢?这个和编译有关,编译时谁在前面谁先调用

2.+initialize()方法

1.initialize源码分析

其实在进行消息慢速查找流程中lookUpImpOrForward我们已经找到了入口。见下图:

案例: JHSPerson类中我们实现initialize,在main函数中我们初始化类;

@implementation JHSPerson : NSObject

+ (void)load{

    NSLog(@"%s", __func__);

}

+ (void)initialize{

    

    NSLog(@"initialize : %s",__func__);

}

@end

int main(int argc, const char * argv[]) {

    @autoreleasepool {

        JHSPerson *p1 = [JHSPerson alloc];

    }

    return 0;

}

lookUpImpOrForward中我们打断点如图:

12.png

在此过程中,首先要确保类已经实现,也就是针对懒加载的类(未实现load方法)进行实现。类实现后,再进行initialize方法的调用。见下图:

13.png

继续跟踪源码,最终找到了方法调用的位置: initializeAndLeaveLocked->initializeAndMaybeRelock->initializeNonMetaClass->callInitialize->objc_msgSend(cls, @selector(initialize))

14.png

  • 通过该源码可以发现: initialize是通过obj_msgSend消息发送的方式进行调用的,与load方法调用有本质区别;load方法是在应用程序加载阶段,通过函数级别调用,而非消息发送。

那么initialize调用逻辑是怎样的呢?关键在initializeNonMetaClass实现中,见下图:

15.png

所以可以得出以下结论:当一个类在发送消息时,采用递归的方式,优先判断父类是否isInitialized(),如果没有会调用对应的initialize方法,因为是消息发送逻辑,所以如果子类没有实现initialize方法,就会调用父类的initialize方法

2.initialize案例验证

依然引用分析load的案例,父类 JHSPerson、子类JHSTeacher及其分类同时实现了load方法initialize方法,见下面代码:

// 父类

@implementation JHSPerson : NSObject

+ (void)load{
    NSLog(@"%s", __func__);
}

+ (void)initialize{
    NSLog(@"initialize : %s",__func__);
}

@end

// 父类分类

@implementation JHSPerson (JHS)

+ (void)load{
    NSLog(@"%s", __func__);
}

+ (void)initialize{
    NSLog(@"initialize : %s",__func__);
}

@end

// 子类

@implementation JHSTeacher : JHSPerson

+ (void)load{
    NSLog(@"%s", __func__);
}

+ (void)initialize{
    NSLog(@"initialize : %s",__func__);
}

@end


// 子类分类

@implementation JHSTeacher (JHS)

+ (void)load{

    NSLog(@"%s", __func__);

}

+ (void)initialize{
    NSLog(@"initialize : %s",__func__);
}

@end

直接启动程序,会发现只打印了load方法initialize并没有触发,这个结果也验证了前面的分析,load方法是由应用程序加载阶段,dyld流程中自动调动。而initialize是在向类发送消息的时候才会被触发。

  1. 创建JHSTeacher对象,运行程序,结果如下:

16.png

  • 首先调用了父类的initialize,再调用子类的initialize
  • 因为分类中也实现了initialize方法,所以会调用分类的initialize,本类不会被调用
  • load方法,先于initialize方法调用
  1. 创建Father对象,运行程序,结果如下:

17.png

  • 不会触发子类的initialize方法
  1. 注意: 如果子类和子类的分类都没有实现initialize方法,在对子类进行发送消息时,父类的initialize方法会被调用两次!见下图:

18.png

  • 在子类进行第一次消息发送时(父类没有调用过的情况下),进行递归处理,确保父类先调用了initialize方法,而子类本身没有实现,所以消息发送流程会找到父类,进行调用一次!所以会调用两次!