美文网首页
类的加载

类的加载

作者: 生产八哥 | 来源:发表于2021-01-30 20:11 被阅读0次
可执行程序生成过程
  • 预编译:展开宏,头文件,生成.i文件
  • 编译:生成抽象语法树AST,AST 是抽象语法树,结构上比代码更精简,遍历起来更快,所以使用 AST 能够更快速地进行静态检查,同时还能更快地生成 IR(中间表示)
  • 汇编:通过 IR 可以生成多份适合不同平台的机器码。对于 iOS 系统,IR 生成的可执行文件就是 Mach-O。
  • 链接(dyld):执行可执行文件后,符号绑定到地址上
    App启动 -> _dyld_start -> _dyld_main,接下来是链接流程里的 环境变量设置、共享缓存、主程序初始化、链接主程序、动态库、弱符合绑定、执行初始化方法、进入主程序入口main函数。

我们重点分析的是执行初始化方法这一步。objc_init方法中的_dyld_objc_notify_register,作用是dyld注册,仅在objc运行时使用。其中两个重要的流程是:

  • map_images:dyld将image(镜像文件)加载进内存时,会触发该函数,主要是管理文件中和动态库中的所有符号,其中的_read_images读取加载类信息,即分类协议方法编号SEL等,将Mach-O中的类信息加载到内存。_read_images一共分为10个大步骤,每一步都在源码的 for(EACH_HEADER)循环里,并有相应的英文注释,感兴趣的一定要结合源码去看,而不是看文章纸上谈兵

  • load_image:dyld初始化image会触发该函数,此方法在map_images之后执行,加载执行load方法。


_read_images中第三步的readClass

readClass主要是读取类,在未调用该方法前,cls只是一个地址,执行该方法后,cls类的名称。如果已经实例化,则从ro中获取name,否则从mach-O的数据data中获取name,这里分析得一般是点击App后第一次来到这里的情况,所以一般是后者。这里还会将cls插入到两个哈希表中,一个是gdb_objc_realized_classes总表,一个是allocatedClasses已开辟内存空间的哈希表类的


_read_images中第九步的realizeClassWithoutSwift

realizeClassWithoutSwift方法主要作用是进行非懒加载类的第一次初始化操作,将类加载进内存。将non-lazy 类加载进内存

==这里注意==:realizeClassWithoutSwift方法的作用是实现类,任何类都会调用这个方法,只是时机不同非懒加载类会在map_images的read_images阶段调用,而懒加载类会在消息发送的时候调用,即在消息慢速查找流程中lookUpImpOrForward中如果类未实现,也会调用realizeClassWithoutSwift,快速查找流程是查找缓存,有缓存就已经说明类已经实现了,已经调用过这个方法了。分类如果是非懒加载类会迫使主类在load_images阶段加载。
  1. 读取macho中的data数据,并设置ro、rwro拷贝一份到rw中的ro
  2. 递归调用realizeClassWithoutSwift完善继承链,以保证类的完整性。设置cxx函数
  3. 调用methodizeClass方法化类:从ro中读取方法列表属性列表协议列表赋值给rw,并返回cls。这里还会对rwe不为空的条件下赋值,但第一次rwe都为空,rwe在分类添加到主类时或者调用class_addXXXX即runtime添加方法、协议、属性时才会初始化创建rwe = cls->data()->extAllocIfNeeded()。下面分析此方法。
methodizeClass方法化类
//拿方法列表源码来举例
method_list_t *list = ro->baseMethods();
if (list) {
    prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
    if (rwe) rwe->methods.attachLists(&list, 1);
}
  • 排序:读取ro->baseMthods,并调用prepareMethodLists中的fixupMethodList方法,根据sel address排序方法,如果方法主类和分类重名,其底层是比较sel(即name),否则比较imp的address地址。这也是我们在进行方法查找流程中对方法列表进行lookUpImpOrForward二分查找的前提:保证方法列表中的方法是有序的
  • ==准备== 分类中的方法、属性、协议
    methodizeClass方法中最后的attachToClassattachToClass中的attachCategories 方法中准备``分类的数据,rwe也在这里初始化创建,rwe = cls->data()->extAllocIfNeeded();属性列表、方法列表、协议列表等贴到rwe中。贴的动作函数是attachLists,分为一维、二维数组等情形,大体流程是将分类的信息内存平移贴在原本数组的最前面,即LRU算法思维,分类的方法优先执行,这也是分类的意义。

时机

到此在attachCategories中分类数据已经准备好了,那最后是什么时机把分类数据贴到主类中去的呢?在attachCategories打住断点会发现不同情况,调用栈也不同。这里分为四种情况。

【情况1】非懒加载类 + 非懒加载分类,其分类数据的加载在load_images方法中的loadAllCategories,首先对类进行加载,然后把分类的信息贴到类中

【情况2】非懒加载类 + 懒加载分类,其数据加载在read_image就加载数据,数据来自macho的data,data在编译时期就已经完成,即data中除了类的数据,还有分类的数据,与类绑定在一起

【情况3】懒加载类 + 懒加载分类 ,其数据加载推迟到 第一次消息时,数据同样来自macho的data,data在编译时期就已经完成

【情况4】懒加载类 + 非懒加载分类,只要分类实现了load,会迫使主类提前加载,即在_read_images中不会对类做实现操作,需要在load_images方法中触发类的数据加载,即rwe初始化,同时加载分类数据。注意此时类还是懒加载类,但是就是会非懒加载类似的提前加载。所以说懒加载类并不一定只会在第一次消息发送的时候加载,还要取决于有没有非懒加载的分类,如果有非懒加载的分类,那么就走的是 load_images 里面的 prepare_load_methods 的 realizeClassWithoutSwift 。

如果主类本身是非懒加载类,会在read_images阶段加载数据,如果是被迫加载,是在load_images阶段,此阶段是运行时机制,所以分类是运行时期决议的。只要有一个非懒加载分类,那么其他分类也会变成非懒加载分类,即使没实现+load。即要加载就全部加载,否则就不加载。

ro rw rwe

因为苹果系统中只有约10%的类修改过rw,所以为了节省内存,又引出了类的额外信息rwe。对分类处理时才会进行初始化rwe

我们已知:类在编译期时,类的一些数据信息保存在 ro 中,包含了类的名称,方法,协议,实例变量等 编译期确定的信息。当类被Runtime加载之后,runtime会为它分配额外的用于 读取/写入 的 rw
ro 是只读的,存放的是 编译期间就确定 的字段信息;而 rw 是在 runtime 时才创建的,它会先将 ro 的内容拷贝一份,再将类的分类、属性、方法、协议等信息添加进去,rw的存在是因为iOS的运行时机制,可以动态读写
而对于存在动态更改行为的类,会将这部分动态的内容提取到rwe中。


rwe

rwe是在methodizeClassattachToClass中的attachCategories中创建的,即auto rwe = cls->data()->extAllocIfNeeded(),为什么在这个时候创建rwe呢?rwe在分类添加到主类时或者调用class_addXXXX即runtime添加方法、协议、属性时才会初始化创建。因为这时候是要往本类中添加属性、方法、协议等,即对原来的 clean memory要进行处理了,就是要修改本类了。存在需要对原始类修改或处理时会初始化,一般在加载分类或通过runtime API添加的时候会初始化rwe,因为分类就是向原类添加方法、协议等。此时会对ro的methodList排序,并把分类的方法加载methodList的最前面。

  1. 以方法为例,首先往rwe中添加本类的方法,即取ro->baseMethods()添加至list.
  2. 以方法为例,往rwe中添加分类的方法,会走到prepareMethodLists方法排序,然后mlists + ATTACH_BUFSIZ - mcount内存平移拿到方法列表添加至list
  3. 最后将list赋值给rwe->methods.注意:这里只有非懒加载类rwe才会创建。我们知道load方法是在load_images才会调用。实现了load方法就会将类由懒加载变为非懒加载

+load方法的意义就是在read_images阶段提前实现类,并在load_images阶段的时候能够顺利调用到+load方法,不然类都没实现,是调用不到的。

分类实现+load 时,在prepare_load_methods 时,会先调用realizeClassWithoutSwift ,确保本类先实现。

attachList方法插入

的方法list就是指分类的方法,添加在数组的最前面的方法排在后面。这里就是运用了LRU算法思维,常用的分类方法放在前面,优先调用。


load调用

call_load_methods

无论项目中是否用到了这个类,调用类和类别中所有未决的 +load 方法,类里面 +load 方法是父类->子类->分类,彼此不会覆盖。
1.通过 objc_autoreleasePoolPush 压栈一个自动释放池。
2.do-while 循环开始,循环调用类的 +load 方法直到找不到为止
3.调用一次分类中的 +load 方法。
4.通过 objc_autoreleasePoolPop 出栈一个自动释放池。

+ load

在runtime源码中,我们可以看到,+load方法是在load_images中通过call_load_methods调用的。
更具体的来说是在运行时加载镜像时,通过prepare_load_methods方法将+load方法准备就绪,而后执行call_load_methods,调用+load方法。

+load方法是系统根据方法地址直接调用,并不是objc_msgSend函数调用(isa,superClass);这就决定了如果子类没有实现+load方法,那么当它被加载时runtime是不会调用父类的+load方法的。

每个类的load函数只会自动调用一次.由于load函数是系统自动加载的,因此不需要再调用[super load],否则父类的load函数会多次执行。如果不可避免的执行多次,需要dispatch_once来控制保证只执行一次放在load里或initialize里的代码。


调试小技巧

  • 在调试底层代码时,会有很多系统的类呼啸而过,若想调试自定义的类,可以加个类名判断,即当前类是否是自定义类。
  • 断点要研究的函数,然后bt打印调用栈反推逻辑。
  • 源码调试时可以把一些系统的private的方法改为public
  • 在 Diagnostics页面, 选中Thread Sanitizer,可以检测到多线程数据竞争问题

相关文章

  • 第一章 类加载过程

    要点 类加载过程 类加载器 一、类加载过程 1.类的加载过程 类的加载 .class文件过程分为:加载---->连...

  • 深入理解jvm类加载机制

    1.什么是类加载? 类加载机制一个很大的体系,包括类加载的时机,类加载器,类加载时机。 1.1类加载过程 加载器加...

  • java基础知识之java类加载器

    1. 什么是类加载器 类加载器就是用来加载类的东西!类加载器也是一个类:ClassLoader 类加载器可以被加载...

  • 《深入理解JVM虚拟机》读书笔记-类加载器&Java模块化系统

    类加载器 一.类加载器 1.1 类与类加载器 类加载器的定义: Java虚拟机设计团队有意把 类加载阶段中 的“ ...

  • JVM类加载入门

    一 类加载顺序 class类加载-->验证-->准备--->解析--->初始化 class类加载:通过类加载器加载...

  • 学习笔记 | JAVA的反射(二)

    利用反射机制动态加载类 、获取类的方法、获取类的属性 编译时刻加载类是静态加载类,运行时加载类是动态加载类 正常创...

  • jvm类加载器详解和如何打破双亲委派机制

    类加载过程: 项目启动的时候,并不是加载项目中的所有类,是在使用的时候加载,类加载器加载类的时候首先加载父类,所以...

  • JVM - ClassLoader

    1. 概述 类加载器实际定义了类的namespace。 2.类加载方式之当前类加载器和指定类加载器 类的加载只有两...

  • java-类加载机制

    类的加载机制 主要关注点: 什么是类的加载 类的生命周期 类加载器 双亲委派模型 什么是类的加载 类的加载指的是将...

  • java类加载器及其原理

    java类加载器 : java中默认有三种类加载器:引导类加载器,扩展类加载器,系统类加载器(也叫应用类加载器) ...

网友评论

      本文标题:类的加载

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