类的加载

2021-01-30  本文已影响0人  生产八哥
可执行程序生成过程

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


_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);
}

时机

到此在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里的代码。


调试小技巧

上一篇 下一篇

猜你喜欢

热点阅读