类的加载(上)

iOS 底层原理 文章汇总

我们初步探索了dyld和objc的关联,也因此引出将类加载到内存中,最关键的就是两个函数map_imagesload_images

  • map_images:主要是管理文件中和动态库中的所有符号,即class、protocol、selector、category等
  • load_images加载执行load方法

静态库、动态库以及自己写的代码通过编译会生成mach-o形式的可执行文件,再从Mach-O中读取到内存。

1320655-972db2887d6a745f.png

map_images:加载镜像文件到内存

map_images方法的主要作用就是将Mach-O可执行文件中的类信息加载到内存

  • 进入map_images的源码

image.png

  • 进入map_images_nolock源码,其关键代码是_read_images

image.png

_read_images 源码实现

_read_images主要是加载类信息(即类、分类、协议等)内存,进入_read_images源码实现,主要分为以下几部分:

  • 条件控制进行的一次加载
  • 修复预编译阶段的@selector的混乱问题
  • 错误混乱的类处理
  • 修复重映射一些没有被镜像文件加载进来的类
  • 修复一些消息
  • 当类里面有协议时:readProtocol 读取协议
  • 修复没有被加载的协议
  • 分类处理
  • 类的加载处理
  • 没有被处理的类,优化那些被侵犯的类
1、条件控制进行的一次加载

在doneOnce流程中通过NXCreateMapTable创建表,即创建一张类的哈希表 gdb_objc_realized_classes,目的是为了方便快捷地查找类

image.png

查看gdb_objc_realized_classes的注释说明,这张哈希表用于存储不在dyld共享缓存中且已命名的类,无论类是否实现

image.png

2.修复预编译阶段的@selector的混乱问题

主要是通过_getObjc2SelectorRefs拿到Mach_O中的静态段__objc_selrefs,遍历列表调用sel_registerNameNoLock将SEL添加到namedSelectors哈希表中

image.png

3、错误混乱的类处理

主要是从Mach-O中取出所有类,在遍历进行处理。当某些类已经被移动了,但未被删除的时候,就会在这里进行处理

image.png

  • 通过断点调试,在未执行readClass方法前,cls只是一个地址
  • 在执行完readClass方法之后,newCls是一个类的名称

image.png

所以到了这一步,类的信息目前仅仅存储了地址+名称

4、修复重映射一些没有被镜像文件加载进来的类

主要是将未映射的Class 和Super Class进行重映射,其中

  • _getObjc2ClassRefs是获取Mach-O中的静态段__objc_classrefs即类的引用
  • _getObjc2SuperRefs是获取Mach-O中的静态段__objc_superrefs即父类的引用
  • 通过注释可以得知,被remapClassRef的类都是懒加载的类,所以最初经过调试时,这部分代码是没有执行的

image.png

5、修复一些消息

主要是通过_getObjc2MessageRefs获取Mach-O的静态段__objc_msgrefs`,并遍历通过fixupMessageRef将函数指针进行注册,并fix为新的函数指针

image.png

6、当类里面有协议时:readProtocol 读取协议

image.png

7、修复没有被加载的协议

image.png

8、分类处理

分类的加载,会在后面的文章中介绍…

image.png

9、类的加载处理

主要是实现类的加载处理,实现非懒加载类

  • 通过_getObjc2NonlazyClassList获取Mach-O的静态段__objc_nlclslist非懒加载类表
  • 通过addClassTableEntry将非懒加载类插入类表,存储到内存,如果已经添加就不会载添加,需要确保整个结构都被添加
  • 通过realizeClassWithoutSwift实现当前的类,因为前面3中的readClass读取到内存的仅仅只有地址+名称,类的data数据并没有加载出来

image.png

10、没有被处理的类,优化那些被侵犯的类

主要是实现没有被处理的类,优化被侵犯的类

image.png

接下来 我们需要重点关注一下readClass以及realizeClassWithoutSwift这两个方法。

readClass:读取类

readClass主要是去读取类,在未执行readClass之前,cls只是一个地址,在执行完readClass之后,返回的newCls已经有了名称

  • 为了让自己定义的LGPerson类进来,我们开readClass源码里面写了一些判断条件
   // 如果想自定义的类进入,自己加一个判断
    const char *LGPersonName = "LGPerson";
    if (strcmp(mangledName, LGPersonName) == 0) {
        auto kc_ro = (const class_ro_t *)cls->data();
        printf("%s -- 研究重点--%s\n", __func__,mangledName);
    }
复制代码
  • readClass源码实现如下,关键代码是addNamedClass和addClassTableEntry
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享