类的加载(上)

474 阅读5分钟

上一个文章中有提到过juejin.cn/post/691944…_objc_init方法中会注册dyld,其中传入了map_images函数load_images函数和unmap_image函数,在分析dyld的加载流程的时候就知道load_images主要是打印所有类的load方法的(这里有个小的优化点就是少写load方法优化加载流程)在探索dyld的注册流程呢,也知道map_images方法肯定是在load_images方法之前调用的(在注册的时候就已经被调用)下面就通过源码探索map_images函数的作用

  • map_image源码探索

    map_image方法源码很简单返回了一个map_images_nolock方法,此时继续向下探索查看map_images_nolock方法
    • map_images_nolock 源码分析 发现_read_images方法是核心方法继续探索_read_images方法
    • _read_images 源码分析 通过源码发现_read_images大致做了十个事情:
      1. 条件控制进行的一次加载
        主要作用是创建一个哈希表存储累的信息便于后续快速找到对应的类,gdb_objc_realized_classes存放的是不在共享内存内的并且已经命名的类,哈希表容量是类总是的4/3
      2. 修复预编译阶段的@selector的混乱问题
        主要作用是:将所有SEL都注册到哈希表中
        通过_getObjc2SelectorRefs拿到MachO中的静态段__objc_selrefs,遍历列表调用sel_registerNameNoLock将SEL添加到namedSelectors哈希表
      3. 错误混乱的类处理
      4. 修复重映射一些没有被镜像文件加载进来的类
      5. 修复一些消息
      6. 当类里面有协议时:readProtocol 读取协议
      7. 修复没有被加载的协议
      8. 分类处理
      9. 类的加载处理下文详细解读
      10. 没有被处理的类,优化那些被侵犯的类
    • readClass 源码分析
      作用就是将Mach-O中的类读取到内存,即插入表中,但是目前的类仅有两个信息:地址以及名称,而mach-O的其中的data数据还未读取出来
    • 类的加载处理详细解读(上文第9步)
      看完readClass再回到_read_images函数 发现类处理中的核心代码是realizeClassWithoutSwift方法再看realizeClassWithoutSwift源码解读
    • realizeClassWithoutSwift 源码分析 看源码之前补充两个知识点:
      1. ro 表示 readOnly,即只读,其在编译时就已经确定了内存,包含类名称、方法、协议和实例变量的信息,由于是只读的,所以属于Clean Memory,而Clean Memory是指加载后不会发生更改的内存
      2. rw 表示 readWrite,即可读可写,由于其动态性,可能会往类中添加属性、方法、添加协议,在最新的2020的WWDC的对内存优化的说明Advancements in the Objective-C runtime - WWDC 2020 - Videos - Apple Developer中,提到rw,其实在rw中只有10%的类真正的更改了它们的方法,所以有了rwe,即类的额外信息。对于那些确实需要额外信息的类,可以分配rwe扩展记录中的一个,并将其滑入类中供其使用。其中rw就属于dirty memory,而 dirty memory是指在进程运行时会发生更改的内存,类结构一经使用就会变成 ditry memory,因为运行时会向它写入新数据,例如 创建一个新的方法缓存,并从类中指向它
        看注释就可以知道该方法的作用是类的首次初始化 从源码中可以看到这里给rw申请了空间但是并没有赋值如下打印从打印结果知道在没有执行methodizeClass方法之前打印方法都是打印不出的,在看methodizeClass方法的源码实现
    • methodizeClass 源码分析 从方法的注释可以知道methodizeClass主要是:
      1. 对类的方法列表、协议列表和属性列表进行修正
      2. 附加category到类上面来 发现源码亦是如此加入了属性、方法、协议
      • prepareMethodLists
        从源码中法先在加入方法之前先调用了prepareMethodLists方法
        prepareMethodLists方法的主要作用是对方法进行排序,应为调用函数在查找方法的时候是二分查找的所以说sel-imp是有排序的,具体prepareMethodLists如何排序,再看源码发现调用了fixupMethodList方法,再看fixupMethodList方法源码发现是通过sel的地址来排序的
      • attachLists 源码分析 attachLists添加方法有三种情况:
        1. 多对多的情况(基本上是用于分类方法的添加,添加分类的属性或者方法的时候会直接传递一个list)
        2. 一对多的情况
        3. 0对一的情况,这种情况就直接将值赋给list就完事了 三个中情况都有个共同点,就是新加的list都会放在老数据的前面
      • attachCategories 源码分析 看注释就知道此方法主要将分类中的方法、协议、属性添加在类中
        并且在在这个方法里面会初始化rweauto rwe = cls->data()->extAllocIfNeeded(); (所以rwe中存储的是分类中的方法、协议、属性) 到这里类的加载也算是完成了
  • 懒加载类和非懒加载类的加载方式

    • 非懒加载类(实现了+load方法)
    • 懒加载类(没有实现+load方法)

      Realize non-lazy classes (for +load methods and static instances) 从注释中就知道如果是懒加载类classref_t const *classlist = hi->nlclslist(&count);这个方法中是获取不到的,所以在read_image中就不会走到realizeClassWithoutSwift方法中所以懒加载类是不会在这里初始化的
      通过打印堆栈信息可以知道如果是懒加载类会在第一次发送消息的时候实现类

  • 类的加载流程图