本文由快学吧个人写作,以任何形式转载请表明原文出处
一、资料准备
objc4-818.2
对应mac的版本是11.1。可根据自己的体系版本挑选能够进行调试的源码。
二、思路
已然说到了类的加载,那么必定要知道类的结构,在类的实质这儿说过,类是个结构体,叫objc_class,是继承于objc_object的,objc_class结构体内有4个成员,也便是类有四个成员 : isa、superClass、cache、bits。
-
上一章map_images映射镜像中,在读取镜像的函数
_read_images
里边找到了类从镜像被加载到内存的步骤,也便是上一章总结里边的2和8。 - 那么类是怎么加载到内存中的?类的结构中的数据又是怎么加载到内存中的?
- 核心函数是 :
readClass
、realizeClassWithoutSwift
。
三、readClass
1. 找到readClass源码
- 在objc4-818.2 —> 查找_objc_init(void) —> map_images —> map_images_nolock —> _read_images。
- 找到类处理的for循环。找到
readClass
如下 :
2. 进入readClass源码
- 解析都在图中的注释中。
- readClass对正常的类做的事情是 : 将类增加到
gdb_objc_realized_classes
表和allocatedClasses
表中,假如类现已在allocatedClasses
表中存在,则只增加元类。 - 增加到
gdb_objc_realized_classes
表中是经过addNamedClass
函数。 - 增加到
allocatedClasses
表中是经过addClassTableEntry
函数。 -
gdb_objc_realized_classes
表的创立是在上一章的四—>1,也便是doneOnce里边创立的。存储的是没有在dyld的同享缓存中存在的类,无论这个类是否完成过。 -
allocatedClasses
表的创立是在上上章objc_init中的runtime_init()创立的。存储的是现已分配过内存的类。 -
gdb_objc_realized_classes
能够看作是allocatedClasses
的超集,是包含了allocatedClasses
表中的内容的。 - 为什么要创立两张表?答 : 便利或许说优化查找。不用每次查找都找大的表(
gdb_objc_realized_classes
)。现已分配过内存的类能够直接到小表allocatedClasses
中查找。 -
addNamedClass
源码 :
-
addClassTableEntry
源码 :
3. readClass源码总结
将类增加进
gdb_objc_realized_classes
表中,也便是增加到 :dyld同享缓存中没有的类的表中。
allocatedClasses
表中假如没有类,也增加到allocatedClasses
表中,这个表是一切分配过内存的类的表。将元类也增加进来。readClass之后,类还是在mach-o中,没有加载到内存中的。
四、realizeClassWithoutSwift
1. 找到realizeClassWithoutSwift源码
-
在objc4-818.2 —> 查找_objc_init(void) —> map_images —> map_images_nolock —> _read_images。
-
找到完成类的for循环中 :
2. 进入realizeClassWithoutSwift源码
1. 判别类是否存在,判别类是否完成过
2. 将类的rw和ro数据加载进去。
3. 递归完成父类和元类的rw和ro的数据加载
4. 设置类的isa,看是否需求设置成纯洁的isa
5. 更新类的父类和元类的数据,修正内存偏移,设置类的实例的大小,设置rw的flags,将子类增加到父类的子类列表里边
6. 修复类的办法、特点、协议的列表的存储。处理分类相关
五、源码调试realizeClassWithoutSwift
源码调试的目的是为了直观的看到,rw和ro到底是在哪一步就加到了cls中的。rwe是不是也在这儿加到了cls中。
有关ro,rw,rwe的内容在有关ro和rw这儿。
1. 在objc-818.2的KCObjcBuild文件夹下创立自己的类,并在类的.m文件中完成+load
之所以要完成+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. 将两个代码段放到如下方位
- read_images源码里边,找到完成懒加载类的源码,将代码段1参加如下方位 :
- realizeClassWithoutSwift源码里边,将代码段2参加如下方位 :
4. 断点怎么打
一定要一个断点,一个断点的运用,先断到了自己创立的类,再给别的当地打断点。由于read_images是循环遍历mach-o静态段去加载类,每一个非懒加载类都会走到realizeClassWithoutSwift
。直接打上一切的断点,走到断点上的时候,cls就不一定是不是自己创立的类了。
所以先断点如下 :
5. 断点调试
- 运转项目,断到如上面的图中3824行的方位。找到自界说的JDMan类
- 再增加断点,增加到
realizeClassWithoutSwift(cls, nil)
方位。让断点跳到这儿。
- 进入
realizeClassWithoutSwift
函数,在自己写的代码段2的方位打上断点。然后让断点跳到这儿。
- 然后点stepOver,跳断点,看能不能履行到如下方位,假如能够,证明现在的cls便是自界说的JDMan类,不是JDMan元类。
- 向下走断点,能够看下ro是什么,这个ro是mach-o中的ro,还没有加载到内存中,也没有放入到类的rw中。断点走到如下方位,lldb看ro。
- 向下走断点,会越过下面的if,进入到else。先走到如下图方位。具体内容看图。
- 再向下走断点,会发现rw的内存空间现已分配好了,rw是一个崭新的rw,没有任何的数据存在里边。
不确定的话,能够用lldb看看。lldb调试如下 :
- 持续向下走断点。过了
rw->set_ro(ro)
之后,rw的ro就被设置好了。
此刻的rw的ro有数据了。自己内部的函数,例如methods()
也能够从ro中获取到数据了。能够lldb试一下从rw拿到数据,如下图 :
为什么说methods()
函数是从ro中获取到数据?还能从哪里获取?经过上面的源码rw->set_ro(ro);
,进入到set_ro()
的源码。
看那个判别if(v.is)
这儿的,类型是rwe的话,才会走这儿。现在的rwe还没有值呢。能够验证rwe此刻是没有值的,如下图操作 :
- 断点还是在这个方位,不要移动。看cls中的rw和ro有值吗?
cls的ro和rw是没有值的。由于rw还没有被赋值给cls的rw。赋值是鄙人面的一步 :cls->setData(rw)
- 移动断点到如下图所示,再看cls的rw和ro,现已有了数据,阐明
cls->setData(rw)
这一步就现已给cls的rw和ro赋值 :
看cls的rw中的数据 :这儿我只看了methods,特点其实也现已出来了,也能够看,没有截图。
看cls的ro中的数据 :
6. 总结
由上面的代码调试能够总结出,在
cls->setData(rw);
之后,类的rw和ro就现已有了数据了。
六、没有分类的类的methodizeClass
methodizeClass
内容有关分类,这儿只说一个非懒加载类进入这儿之后做什么。不包括分类,分类的内容后面再说。
1. 进入methodizeClass
源码
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
函数里,如图所示的方位 :
-
jd_isMeta判别了进来的便是JDMan类(在上面(五)里边创立好的),而不是JDMan元类。
-
运转818.2,会走到断点方位。
-
然后向下履行断点,走到如图方位,看
rwe
:
- rwe是没有的,所以根本不会进到if里边。
- rwe是由于运转时动态的对类的特点或许办法或许协议进行更改,才会生成的,具体内容看这儿。
3.attachToClass对没有rwe的非懒加载类做了什么
- 进入
attachToClass
源码并在如图方位加上断点 :
- 会发现
attachToClass
相当于未对没有rwe的非懒加载类做任何事情。 - 也便是说,在整个
methodizeClass
函数中,是给了开发者一个信息:类的完成和加载完成了,该处理分类了。