iOS底层探究-----load和initialize

463 阅读3分钟

前言

根据上篇文章---- 程序加载流程的探究,知道了dyld的宏观加载流程,还有dyld_objc_init对接调用关系。通过_dyld_objc_notify_register函数,知道主要是传值&map_imagesload_imagesCF262A7F-C44D-4BB4-86ED-518E3510921E.pngmap_imagesload_images函数的调用,也有简单的介绍,那么这篇文章,则对上篇进行一些延伸。

资源准备

进入正文

load_images()

20B506C3-64AA-43EC-97A9-D6C0B1D4F9D2.pngload_images()函数里面做了:

  • 判断是否加载分类,如果没有,就加载所有分类loadAllCategories()
  • 准备加载方法prepare_load_methods()
  • 调用加载方法call_load_methods()

prepare_load_methods()

那么我们看下是如何准备的了? 8CEE1E43-E120-4D2E-A03E-DEDA6B268F70.png 准备流程:

  • 通过for循环,递归地为cls和任何未加载的父类安排加载---schedule_class_load()
  • 通过for循环,与外部的真实关联、cls类初始化、分配读写数据、返回类的真实类结构 --- realizeClassWithoutSwift()
  • 通过for循环,将类别添加到可加载列表 --- add_category_to_loadable_list()

schedule_class_load()

92F371A6-ED4E-4AAF-B628-FD2BBF0C2F16.png 通过这个源码块,可以知道:

  • 通过递归,先把父类的load写入表里面,然后再写入子类的load--- schedule_class_load()
  • 将类添加到可加载列表 --- add_class_to_loadable_list()

add_class_to_loadable_list()

24DE45E3-F0C5-42B7-A524-58476B1CF366.png 通过这个源码块,可以知道:

  • 如果父类和子类还有没进行load加载的,直接返回,继续递归加载;
  • 如果没有加载方法,实现一个加载方法,就重新加载;
  • 将类添加到可加载列表 -- loadable_classes

add_category_to_loadable_list()

88E72F1E-B676-43AE-B72E-BA33339E1502.png 分类的处理和类的处理大致相同。最后将分类添加进入loadable_categories里面

当准备加载完毕后,就是调用加载了。

call_load_methods()

9E400E2D-6AD1-4C7C-BEAF-4C88D091E234.png 通过do-while循环,先调用类的load,然后再调用分类的load

call_class_loads()

7CD8C9B6-4B67-411D-A2F1-7608E9FA6F2F.png

  • 类的load调用:通过loadable_classes里面拿到load方法,并调用。

call_category_loads()

ADEE4A3A-247C-4F6B-A0A5-492D50AA32FD.png

  • 分类的load调用:通过loadable_categories里面拿到load方法,并调用。

load函数小结

通过上文的分析,当执行load_images时,先是准备,先把类的load写入loadable_classes里面,再把分类的load写入loadable_categories里面;再是调用,先是类的load调用,从loadable_classes里面获取,再是分类的load调用,从loadable_categories里面获取。

可以再通过一个案例来验证下:创建LGPerson继承于NSObjectLGSon继承于LGPersonLGGrandSon继承于LGSon,再在他们三者各自的.m文件,调用

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

最后在main()里面初始化LGGrandSon这个类: 7B5C363B-B72F-4EA5-A320-B76D9F6FFA71.png 这样就印证了我们的分析。

一提到load函数,立马就想到了initialize(初始化)函数。这也是在面试中,经常会问到的。那么接下来,就对initialize进行分析,和load函数做个对比。

initialize()

根据消息查找流程,我们知道在消息的慢速查找里面lookUpImpOrForward(),有类的实现realizeAndInitializeIfNeeded_locked(),入下图所示: 7599815F-525D-4ACF-B19D-6DE2FD20ACFF.png

realizeAndInitializeIfNeeded_locked()

realizeAndInitializeIfNeeded_locked()函数里面分析,类必须是先要实现,也就是先的进行load(),然后才能initialize()初始化,如下图: A0A744E7-9064-4B96-9933-FE2D7FF9D7E7.png 该函数的工作内容:

  • 判断类是否实现,如果没实现,就先进行实现
  • 当类实现后,再判断类是否初始化,如果没有初始化,就进行初始化

initializeAndMaybeRelock()

986715E9-CD87-4EFD-8CC3-093247F8320A.png 该函数的工作内容:

  • 检测类是否已经初始化,如果初始化,就直接返回;
  • 检测非元类是否实现,如果没有实现,就需要进行实现;
  • 初始化非元类。

initializeNonMetaClass()

338BABEF6E456042746C18E3ABBF16DC.png 该函数的工作内容:

  • 递归初始化,从父类开始----initializeNonMetaClass();
  • 当是进行初始化是,直接调用callInitialize初始化函数;

callInitialize()

AFB7B811-EADA-45FF-9409-FBF2080C84AF.png 该函数的工作内容:

  • initialize是通过obj_msgSend发送消息,所以其调用方式是:消息发送;

继续沿用刚才的例子,在LGPersonLGSonLGGrandSon三者各自的.m文件,添加:

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

接着再运行工程: EE0778A7-DCE2-4740-878F-2AD02EB498FE.png 根据打印结果,可以知道:

  • 先调用父类的initialize,后调用子类的initialize
  • 当分类中也有initialize,就会重写类里面的initialize
  • load先调用,initialize后调用。

initializeload对比

initializeload有本质的却别:

  • load函数是在应用程序加载阶段,dyld流程中自动调动的;
  • initialize是在向类发送消息的时候才会被触发。