本文由快学吧个人写作,以任何方式转载请表明原文出处
一、资料预备
objc4-818.2
对应mac的版本是11.1。可依据自己的体系版本挑选能够进行调试的源码。
二、思路
- 类的加载(上)和类的加载(下)中知道了非懒加载类和懒加载类是怎样从镜像加载到内存中并完结的。
- 在
methodizeClass
源码中有关分类的处理还没有看过。那么分类的实质是什么? - 分类是怎样和主类联系上的,分类又是怎样完结的?
三、分类的实质
设置main.m中的代码,创立一个JDMan的分类 :
经过clang编译main.m文件,生成编译文件检查分类的实质。一定要进入到main.m所在文件夹下,再执行clang指令。
clang -rewrite-objc main.m -o main.cpp
1. 实质是结构体category_t
在main.cpp中能够看到分类的实质是结构体category_t
。
在818源码中能够查找一下:struct category_t
name : 分类的姓名
cls : 哪个类的分类
instanceMethods : 分类的实例办法列表,类型为
method_list_t
。classMethods : 分类的类办法列表,类型为
method_list_t
。protocols : 分类的协议列表,类型为
protocol_list_t
。instanceProperties : 分类中的特色,类型为
property_list_t
。官方注释 :下面的不常见,不说了。
2. 分类的特色
- 在分类中经过经过
@property
增加的特色是没有setter和getter办法的,和类不相同。- 分类的特色增加setter和getter办法能够经过相关目标来设置。后边再说。
在上面编译的main.cpp文件中也能够看出来,分类中的办法列表中不自带setter和getter办法 :
四、探究分类加载的预备
1. 分类加载的总体思路
分类的加载就要分红4种状况了,因为分类是依附于类的。
删除去main.m中创立的JDPerson分类,以文件的方式来创立一下。用来测验分类的加载。创立成果如下图 :
main.m如下图 :
2. 分类的加载是从哪里进行的
- 思路 :
- 类的完结都是经过
realizeClassWithoutSwift
,不管是非懒加载类仍是懒加载类。- 在
realizeClassWithoutSwift
中的methodizeClass
中发现了官方注释Attach categories.
- 在官方注释下面的
methodizeClass
源码中的attachToClass
源码中发现了attachCategories
,经过命名猜想是和分类相关。attachCategories
源码中有对rwe的操作。而rwe是运转时动态修正类才会呈现的结构。
-
大局查找
attachCategories
,找到load_categories_nolock
呈现了对它调用。 -
大局查找
load_categories_nolock
,找到了loadAllCategories
。
- 大局查找
loadAllCategories
,找到了load_images
-
load_images
是在之前的objc_init中注册回调dyld,使其加载镜像的函数。 -
在map_images一章中见过
load_images
。是经过dyld的注册回调,用来加载镜像。 -
从查找的流程看,是契合map_images这章中的
read_images
中对分类的处理的官方注释的。 -
那么完结类的
realizeClassWithoutSwift
中的methodizeClass
,它的attachToClass
是没有对类的分类进行操作吗?
3. 创立分类进行测验
1. JDMan和main.m中的代码
main.m :
这儿删除去了上面探究分类实质的时分在main.m中创立的分类代码。
JDMan :
2. 创立分类
3. 测验用的自己写的代码段 :
代码段1 :
const char *qudaodemingzi = cls->mangledName();
const char *JDManName = "JDMan";
if (strcmp(qudaodemingzi, JDManName) == 0) {
printf("找到自己界说的类了 : %s 从函数 : %s 拿到的\n",qudaodemingzi,__func__);
}
代码段2 :
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("找到自己界说的类了:%s 从函数 : %s 拿到的\n",qudaodemingzi,__func__);
}
}
代码段3 :
const char *qudaodemingzi = cls->mangledName();
const char *JDManName = "JDMan";
if (strcmp(qudaodemingzi, JDManName) == 0) {
auto jd_isMeta = cls->isMetaClass();
if (!jd_isMeta) {
printf("找到自己界说的类了 :%s 从函数 : %s 拿到的\n",qudaodemingzi,__func__);
}
}
4. 将代码段贴到源码中
用到的代码段不相同,也是依据类的完结状况来确定的。能够自行调整。
-
read_images
中,找到非懒加载类的完结,用代码段1 :
-
realizeClassWithoutSwift
中,用代码段2 :
-
methodizeClass
中,用代码段3 :
-
attachToClass
中,在if判别平分别加了代码段3 :
(1). attachToClass
的if外部 :
(2). attachToClass
的if内部 :
- 进入
load_categories_nolock
,参加代码段3 ,稍作修正,将printf
里边直接改成load_categories_nolock
,因为这是个迭代器,所以__func__
只会显现operator()
,改成如下图 :
- 进入
attachCategories
,参加代码段3 :
五、只要一个分类,分类的加载状况
就如上面创立的,JDMan只要一个分类JDMan(LA)。
1. 类非懒加载+分类非懒加载
在read_images
、realizeClassWithoutSwift
、load_categories_nolock
、attachCategories
中自界说的代码段中参加断点,:
运转项目,成果如下 :
- 先进入了
read_images
的断点 :
- 再进入了
realizeClassWithoutSwift
的断点 :
- 然后进入了
load_categories_nolock
的断点 :
- 最后进入了
attachCategories
的断点 :
- 再运转,就会完结程序的运转,得到了流程如下 :
- 能够发现,是没有进入
attachToClass
的if
判别的。
小结 :
非懒加载类 + 非懒加载分类的加载 :
(1). 非懒加载类经过:
read_images
—>realizeClassWithoutSwift
—>methodizeClass
—>attachToClass
先进行加载和完结。(2). 非懒加载分类经过 :
load_images
—>loadAllCategories
—>load_categories_nolock
—>attachCategories
再进行加载和完结。并没有进入attachToClass
的if
判别的流程。
2. 类非懒加载+分类懒加载
首要注释掉JDMan(LA).m
这个分类中的+(void)load
办法。分类变成懒加载。
运转项目,成果如下 :
- 先进入了
read_images
的断点 :
- 然后进入了
realizeClassWithoutSwift
的断点 :
- 项目直接运转完毕,看自界说代码段打印的成果 :
-
并没有进入
load_images
的流程。那么分类是否是和类一同加载的? -
从头运转项目,将断点走到
realizeClassWithoutSwift
里边。然后向下执行断点,到auto isMeta = ro->flags & RO_META;
这一行 :
- 使用lldb检查ro的数据 :
小结 :
非懒加载类 + 懒加载分类 :
懒加载分类的数据会在编译的时分就和非懒加载类一同被存入了非懒加载类的ro中。在非懒加载类加载的时分,一同加载到内存。不需求经过
attachCategories
等过程再进行加载。
3. 类懒加载+分类非懒加载
注释掉JDMan
类中的+(void)load
。翻开JDMan(LA)
分类中的+(void)load
。
运转项目,成果如下 :
- 先进入了
read_images
的断点 :
2.然后进入了realizeClassWithoutSwift
的断点 :
- 项目直接运转完毕,看自界说代码段打印的成果 :
- 并没有进入
load_images
的流程。那么分类非懒加载是否会使类也成为非懒加载?在read_images
非懒加载类的循环那里,参加如下测验代码,如图 :
- 再次运转818.2项目。
-
JDMan
呈现在了非懒加载类中,证明非懒加载的分类使类也变成了非懒加载的。那么分类的数据是否也现已存储到了类中?从头运转818.2,然后使断点执行到realizeClassWithoutSwift
中,移动断点到ro下面,检查ro中的数据。
小结 :
懒加载类 + 非懒加载分类 :
非懒加载的分类会使懒加载的类变成非懒加载,分类数据和类的数据在编译期一同被写入ro。在类加载的时分,一同加载到内存。不需求经过
attachCategories
等过程再进行加载。
4. 类懒加载+分类懒加载
翻开类和分类的+(void)load
办法。在main.m
中让类被调用一次,例如在main.m
参加JDMan *aMan = [[JDMan alloc] init];
。
运转项目,成果如下 :
- 进入了
realizeClassWithoutSwift
的断点,只不过是经过lookUpImpOrForward
进入的 :
-
然后就直接运转完结,不会走其他的断点。
-
那么分类是否也和上面相同,直接和类一同完结了加载?从头运转项目,看ro的数据 :
小结 :
懒加载类+懒加载分类 : 类会在第一次被调用的时分完结完结和加载,分类的完结和加载与类一同完结。分类的数据存储在ro中。
六、总结
- 分类实质是结构体
category_t
。 - 分类中增加的特色不会自动生成setter和getter办法,需求用到相关目标。
- 假如只要一个分类的状况下,分类的完结和加载状况如下 :
(1). 非懒加载类 + 非懒加载分类 :
非懒加载类的加载 :
map_images
—>read_images
—>realizeClassWithoutSwift
—>methodizeClass
—>attachToClass
非懒加载分类的加载 :
load_images
—>loadAllCategories
—>load_categories_nolock
—>attachCategories
只要一个分类,非懒加载分类不会进入类的
methodizeClass
—>attachToClass
—>attachCategories
流程中进行加载。(2). 非懒加载类 + 懒加载分类 :
分类的数据会在编译期就被写入类的ro中。伴随着类的加载完结加载。不进入
attachCategories
。(3). 懒加载类 + 非懒加载分类 :
非懒加载分类会使懒加载的类变成非懒加载的类,分类的数据也会在编译期就被写入到类的ro中。分类会伴随着类的加载完结加载。不进入
attachCategories
。(4). 懒加载类 + 懒加载分类 :
分类的数据也会在编译期就被写入类的ro中。当类第一次被调用,经过
lookUpImpOrForward
进行完结和加载的时分,分类伴随着一同完结。不进入attachCategories
。