本文由快学吧个人写作,以任何形式转载请表明原文出处

一、资料准备

objc4-818.2

对应mac的版本是11.1。可根据自己的体系版本挑选能够进行调试的源码

二、思路

已然说到了类的加载,那么必定要知道类的结构,在类的实质这儿说过,类是个结构体,叫objc_class,是继承于objc_object的,objc_class结构体内有4个成员,也便是类有四个成员 : isa、superClass、cache、bits。

  1. 上一章map_images映射镜像中,在读取镜像的函数_read_images里边找到了类从镜像被加载到内存的步骤,也便是上一章总结里边的2和8。
  2. 那么类是怎么加载到内存中的?类的结构中的数据又是怎么加载到内存中的?
  3. 核心函数是 : readClassrealizeClassWithoutSwift

三、readClass

1. 找到readClass源码
  1. 在objc4-818.2 —> 查找_objc_init(void) —> map_images —> map_images_nolock —> _read_images。
  2. 找到类处理的for循环。找到readClass如下 :

二十、app加载流程(四)类的实现和加载(上)

2. 进入readClass源码

二十、app加载流程(四)类的实现和加载(上)

  1. 解析都在图中的注释中。
  2. readClass对正常的类做的事情是 : 将类增加到gdb_objc_realized_classes表和allocatedClasses表中,假如类现已在allocatedClasses表中存在,则只增加元类。
  3. 增加到gdb_objc_realized_classes表中是经过addNamedClass函数。
  4. 增加到allocatedClasses表中是经过addClassTableEntry函数。
  5. gdb_objc_realized_classes表的创立是在上一章的四—>1,也便是doneOnce里边创立的。存储的是没有在dyld的同享缓存中存在的类,无论这个类是否完成过。
  6. allocatedClasses表的创立是在上上章objc_init中的runtime_init()创立的。存储的是现已分配过内存的类。
  7. gdb_objc_realized_classes能够看作是allocatedClasses的超集,是包含了allocatedClasses表中的内容的。
  8. 为什么要创立两张表?答 : 便利或许说优化查找。不用每次查找都找大的表(gdb_objc_realized_classes)。现已分配过内存的类能够直接到小表allocatedClasses中查找。
  9. addNamedClass源码 :

二十、app加载流程(四)类的实现和加载(上)

  1. addClassTableEntry源码 :

二十、app加载流程(四)类的实现和加载(上)

3. readClass源码总结
  1. 将类增加进gdb_objc_realized_classes表中,也便是增加到 :dyld同享缓存中没有的类的表中。

  2. allocatedClasses表中假如没有类,也增加到allocatedClasses表中,这个表是一切分配过内存的类的表。将元类也增加进来。

  3. readClass之后,类还是在mach-o中,没有加载到内存中的。

四、realizeClassWithoutSwift

1. 找到realizeClassWithoutSwift源码
  1. 在objc4-818.2 —> 查找_objc_init(void) —> map_images —> map_images_nolock —> _read_images。

  2. 找到完成类的for循环中 :

二十、app加载流程(四)类的实现和加载(上)

2. 进入realizeClassWithoutSwift源码
1. 判别类是否存在,判别类是否完成过

二十、app加载流程(四)类的实现和加载(上)

2. 将类的rw和ro数据加载进去。

二十、app加载流程(四)类的实现和加载(上)

3. 递归完成父类和元类的rw和ro的数据加载

二十、app加载流程(四)类的实现和加载(上)

4. 设置类的isa,看是否需求设置成纯洁的isa

二十、app加载流程(四)类的实现和加载(上)

二十、app加载流程(四)类的实现和加载(上)

5. 更新类的父类和元类的数据,修正内存偏移,设置类的实例的大小,设置rw的flags,将子类增加到父类的子类列表里边

二十、app加载流程(四)类的实现和加载(上)

6. 修复类的办法、特点、协议的列表的存储。处理分类相关

二十、app加载流程(四)类的实现和加载(上)

五、源码调试realizeClassWithoutSwift

源码调试的目的是为了直观的看到,rw和ro到底是在哪一步就加到了cls中的。rwe是不是也在这儿加到了cls中。

有关ro,rw,rwe的内容在有关ro和rw这儿。

1. 在objc-818.2的KCObjcBuild文件夹下创立自己的类,并在类的.m文件中完成+load

二十、app加载流程(四)类的实现和加载(上)

二十、app加载流程(四)类的实现和加载(上)

之所以要完成+load是由于realizeClassWithoutSwift的官方注释里边写了,完成的对错懒加载的类。正常的JDMan是懒加载的,增加上+load办法,就会让它的加载机遇提前,不必再等到调用时再加载。

2. 需求用的代码,自己写的

这两段代码是为了在调试realizeClassWithoutSwift办法时直接定位到自界说的类,过滤掉体系的类。

代码段1 :

            //mangledName是在objc_class的结构体中找到的,在上面的readClass里边有一
            //个类似的nonlazyMangledName,所以找到了它
            const char *qudaodemingzi = cls->mangledName();
            const char *JDManName = "JDMan";
            if (strcmp(qudaodemingzi, JDManName) == 0) {
                printf("找到自己界说的类了");
            }

代码段2 :

    //仅仅比上面的代码多了一个从类的ro中取出flags,用来保证探索的不是元类
    //毕竟元类我只能找到类办法,但是类能够经过lldb找到rw和ro中的特点和实例办法
    const char *qudaodemingzi = cls->mangledName();
    const char *JDManName = "JDMan";
    if (strcmp(qudaodemingzi, JDManName) == 0) {
        auto jd_ro = (const class_ro_t *)cls->data();
        auto jd_isMeta = jd_ro->flags & RO_META;
        if (!jd_isMeta) {
            printf("找到自己界说的类了");
        }
    }
3. 将两个代码段放到如下方位
  1. read_images源码里边,找到完成懒加载类的源码,将代码段1参加如下方位 :

二十、app加载流程(四)类的实现和加载(上)

  1. realizeClassWithoutSwift源码里边,将代码段2参加如下方位 :

二十、app加载流程(四)类的实现和加载(上)

4. 断点怎么打

一定要一个断点,一个断点的运用,先断到了自己创立的类,再给别的当地打断点。由于read_images是循环遍历mach-o静态段去加载类,每一个非懒加载类都会走到realizeClassWithoutSwift。直接打上一切的断点,走到断点上的时候,cls就不一定是不是自己创立的类了。

所以先断点如下 :

二十、app加载流程(四)类的实现和加载(上)

5. 断点调试
  1. 运转项目,断到如上面的图中3824行的方位。找到自界说的JDMan类

二十、app加载流程(四)类的实现和加载(上)

  1. 再增加断点,增加到realizeClassWithoutSwift(cls, nil)方位。让断点跳到这儿。

二十、app加载流程(四)类的实现和加载(上)

  1. 进入realizeClassWithoutSwift函数,在自己写的代码段2的方位打上断点。然后让断点跳到这儿。

二十、app加载流程(四)类的实现和加载(上)

  1. 然后点stepOver,跳断点,看能不能履行到如下方位,假如能够,证明现在的cls便是自界说的JDMan类,不是JDMan元类。

二十、app加载流程(四)类的实现和加载(上)

  1. 向下走断点,能够看下ro是什么,这个ro是mach-o中的ro,还没有加载到内存中,也没有放入到类的rw中。断点走到如下方位,lldb看ro。

二十、app加载流程(四)类的实现和加载(上)

  1. 向下走断点,会越过下面的if,进入到else。先走到如下图方位。具体内容看图。

二十、app加载流程(四)类的实现和加载(上)

  1. 再向下走断点,会发现rw的内存空间现已分配好了,rw是一个崭新的rw,没有任何的数据存在里边。

二十、app加载流程(四)类的实现和加载(上)

不确定的话,能够用lldb看看。lldb调试如下 :

二十、app加载流程(四)类的实现和加载(上)

  1. 持续向下走断点。过了rw->set_ro(ro)之后,rw的ro就被设置好了。

二十、app加载流程(四)类的实现和加载(上)

此刻的rw的ro有数据了。自己内部的函数,例如methods()也能够从ro中获取到数据了。能够lldb试一下从rw拿到数据,如下图 :

二十、app加载流程(四)类的实现和加载(上)

为什么说methods()函数是从ro中获取到数据?还能从哪里获取?经过上面的源码rw->set_ro(ro);,进入到set_ro()的源码。

二十、app加载流程(四)类的实现和加载(上)

看那个判别if(v.is)这儿的,类型是rwe的话,才会走这儿。现在的rwe还没有值呢。能够验证rwe此刻是没有值的,如下图操作 :

二十、app加载流程(四)类的实现和加载(上)

  1. 断点还是在这个方位,不要移动。看cls中的rw和ro有值吗?

二十、app加载流程(四)类的实现和加载(上)

cls的ro和rw是没有值的。由于rw还没有被赋值给cls的rw。赋值是鄙人面的一步 :cls->setData(rw)

  1. 移动断点到如下图所示,再看cls的rw和ro,现已有了数据,阐明cls->setData(rw)这一步就现已给cls的rw和ro赋值 :

看cls的rw中的数据 :这儿我只看了methods,特点其实也现已出来了,也能够看,没有截图。

二十、app加载流程(四)类的实现和加载(上)

二十、app加载流程(四)类的实现和加载(上)

看cls的ro中的数据 :

二十、app加载流程(四)类的实现和加载(上)

二十、app加载流程(四)类的实现和加载(上)

6. 总结

由上面的代码调试能够总结出,在cls->setData(rw);之后,类的rw和ro就现已有了数据了。

六、没有分类的类的methodizeClass

methodizeClass内容有关分类,这儿只说一个非懒加载类进入这儿之后做什么。不包括分类,分类的内容后面再说。

1. 进入methodizeClass源码

二十、app加载流程(四)类的实现和加载(上)

二十、app加载流程(四)类的实现和加载(上)

二十、app加载流程(四)类的实现和加载(上)

2. 为什么说没有分类的非懒加载类的rwe是空的

增加一行自界说的代码段 :

    const char *qudaodemingzi = cls->mangledName();
    const char *JDManName = "JDMan";
    if (strcmp(qudaodemingzi, JDManName) == 0) {
        auto jd_isMeta = cls->isMetaClass();
        auto jd_rw = cls->data();
        auto jd_ro = jd_rw->ro();
        if (!jd_isMeta) {
            printf("找到自己界说的类了\n");
        }
    }

methodizeClass函数里,如图所示的方位 :

二十、app加载流程(四)类的实现和加载(上)

  1. jd_isMeta判别了进来的便是JDMan类(在上面(五)里边创立好的),而不是JDMan元类。

  2. 运转818.2,会走到断点方位。

  3. 然后向下履行断点,走到如图方位,看rwe :

二十、app加载流程(四)类的实现和加载(上)

  1. rwe是没有的,所以根本不会进到if里边。
  2. rwe是由于运转时动态的对类的特点或许办法或许协议进行更改,才会生成的,具体内容看这儿。
3.attachToClass对没有rwe的非懒加载类做了什么
  1. 进入attachToClass源码并在如图方位加上断点 :

二十、app加载流程(四)类的实现和加载(上)

  1. 会发现attachToClass相当于未对没有rwe的非懒加载类做任何事情。
  2. 也便是说,在整个methodizeClass函数中,是给了开发者一个信息:类的完成和加载完成了,该处理分类了。