前面已经探究了类的加载,类的加载分为懒加载类和非懒加载类,他们有不同加载流程,下面来探究下分类的加载,以及分类和主类之间加载不同的情况
一. 分类的加载
分类的底层结构是结构体categor_t:
struct category_t {
const char *name; // 分类名 比如 `YJPerson(YJ)`这里的 name 就是 `YJ`
classref_t cls; // 分类所属类 `YJPerson`
WrappedPtr<method_list_t, method_list_t::Ptrauth> instanceMethods; // 分类中的 实例方法
WrappedPtr<method_list_t, method_list_t::Ptrauth> classMethods; // 分类中的 类方法
struct protocol_list_t *protocols; // 协议
struct property_list_t *instanceProperties; // 属性
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
protocol_list_t *protocolsForMeta(bool isMeta) {
if (isMeta) return nullptr;
else return protocols;
}
};
分类一般是加载到主类中的,什么时候加载以及加载的过程,这是下面要探究的
1.1 分类加载引入
2020WWDC 类的优化中苹果为分类和动态添加专门分配的了一块内存rwe,因为rwe属于dirty memory所以肯定是需要动态开辟内存。下面从class_rw_t中去查找相关rwe的源码
struct class_rw_t {
... // 省略部分代码
class_rw_ext_t *ext() const {
return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);
}
class_rw_ext_t *extAllocIfNeeded() {
auto v = get_ro_or_rwe();
// 判断rwe是否存在
if (fastpath(v.is<class_rw_ext_t *>())) {
// 如果已经有rwe直接返回地址指针
return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
} else {
// 为rwe开辟内存并且返回地址指针
return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
}
}
class_rw_ext_t *deepCopy(const class_ro_t *ro)
return extAlloc(ro, true);
}
... // 省略部分代码
}
源码中显示如果你主要用到rwe,肯定会调用extAllocIfNeeded()。全局搜索extAllocIfNeeded(),发现调用的地方很多有attachCategories、demangledName、class_setVersion、addMethods_finish、class_addProtocol、_class_addProperty、objc_duplicateClass方法。从这些方法中发现一些是动态添加的方法,关于分类的方法,还有一些关于类名、设置类的版本,以及修复类。今天探究分类的加载流程,所以定位到attachCategories
1.2 反推 attachCategories
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t
cats_count, int flags)
{
... // 省略部分代码
constexpr uint32_t ATTACH_BUFSIZ = 64;
// 这里都是 指针数组,即数组里存放的都是对应类型的指针
method_list_t *mlists[ATTACH_BUFSIZ];
property_list_t *proplists[ATTACH_BUFSIZ];
protocol_list_t *protolists[ATTACH_BUFSIZ];
uint32_t mcount = 0;
// 获取rwe
auto rwe = cls->data()->extAllocIfNeeded();
...
// 遍历所有的分类,但是分类中的方法一般不要超过64个分类,不然苹果会认为你这个人有问题
for (uint32_t i = 0; i < cats_count; i++) {
auto& entry = cats_list[i];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
// 如果你的分类的个数超过64个,那么把这64个分类的方法列表加载到主类中
// 把后面的数据继续放到 mlists[]中
if (mcount == ATTACH_BUFSIZ) {
prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
rwe->methods.attachLists(mlists, mcount);
mcount = 0; //mcount 设置为0
}
// 如果 mcount = 0,mlist存放的位置在63个位置,总共是0 ~ 63
// ATTACH_BUFSIZ = 64, ++mcount = 1, 64-1=63
mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
fromBundle |= entry.hi->isBundle();
}
// 属性的
property_list_t *proplist = entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
if (propcount == ATTACH_BUFSIZ) {
rwe->properties.attachLists(proplists, propcount);
propcount = 0;
}
proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
}
// 协议的
protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
if (protolist) {
if (protocount == ATTACH_BUFSIZ) {
rwe->protocols.attachLists(protolists, protocount);
protocount = 0;
}
protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
}
}
if (mcount > 0) {
// 在将分类方法添加到主类之前将方法进行排序
// 地址偏移还记得d+n嘛,这里:mlists = d,n = ATTACH_BUFSIZ - mcount
// mlists + ATTACH_BUFSIZ - mcount = d + n
// 此时的mlists + ATTACH_BUFSIZ - mcount 是一个二维指针,里面存放的是方法列表的首地址
prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
NO, fromBundle, __func__);
// 在将分类方法添加到主类方法中
rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
if (flags & ATTACH_EXISTING) {...}
}
// 在将分类属性添加到主类属性中
rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
// 在将分类协议添加到主类协议中
rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
... //省略部分代码
}
attachCategories准备分类的数据,然后调用attachLists将数据添加到主类中,那么到底哪些地方调用attachCategories方法,全局搜索attachCategories
源码显示 attachToClass 中调用了 attachCategories 方法
源码显示 load_categories_nolock 中调用了 attachCategories 方法
1.2.1 attachToClass 流程
全局搜索 attachToClass,只有methodizeClass调用了attachToClass
static void methodizeClass(Class cls, Class previously)
{ ... //省略部分代码
// Attach categories.
if (previously) {
if (isMeta) {
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_METACLASS);
} else {
// When a class relocates, categories with class methods
// may be registered on the class itself rather than on
// the metaclass. Tell attachToClass to look for those.
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_CLASS_AND_METACLASS);
}
}
objc::unattachedCategories.attachToClass(cls, cls,
isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
... //省略部分代码
}
methodizeClass这个方法很熟悉realizeClassWithoutSwift调用,而previously条件决定是否调用条件里面的attachToClass方法,而previously是作为参数传进来的。最终找到是realizeClassWithoutSwift传进来的,全局搜索realizeClassWithoutSwift,调用的地方传入的previously=nil,所以attachToClass里面的previously条件不会走,只能最后的attachToClass方法previously作为备用参数,这种设计可能是苹果内部调试用的attachToClass流程:_read_images-->realizeClassWithoutSwift-->methodizeClass-->attachToClass-->attachCategories-->attachLists
1.2.2 load_categories_nolock 流程
全局搜索 load_categories_nolock
static void loadAllCategories() {
mutex_locker_t lock(runtimeLock);
for (auto *hi = FirstHeader; hi != NULL; hi = hi->getNext()) {
load_categories_nolock(hi);
}
}
loadAllCategories方法中调用load_categories_nolock。全局搜索loadAllCategories
static bool didInitialAttachCategories = false;
void
load_images(const char *path __unused, const struct mach_header *mh)
{
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
loadAllCategories();
}
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
load_images 调用loadAllCategories,load_images在dyld中调用
didInitialAttachCategories默认是false,当执行完loadAllCategories()后自动将didInitialAttachCategories设为true,其实就是只调用一次loadAllCategories()- 当
objc向dyld完成注册回调后didCallDyldNotifyRegister=true load_categories_nolock流程:load_images-->loadAllCategories-->load_categories_nolock-->attachLists
1.2.3 attachLists
attachLists 是核心方法,attachLists作用是将分类数据加载到主类中
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
// hasArray()如果array()存在进入判断
if (hasArray()) {
// many lists -> many lists
// oldCount = 获取array()->lists的个数
uint32_t oldCount = array()->count;
// 新的个数 = oldCount + 新添加的
uint32_t newCount = oldCount + addedCount;
// 根据`newCount`开辟内存,类型是 array_t
array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
// 设置新数组的个数等于`newCount`
newArray->count = newCount;
// 设置原有数组的个数等于`newCount`
array()->count = newCount;
// 遍历原有数组中list将其存放在newArray->lists中 且是放在数组的末尾
for (int i = oldCount - 1; i >= 0; i--)
newArray->lists[i + addedCount] = array()->lists[i];
// 遍历二维指针`addedLists`中的list将其存放在newArray->lists中 且是从开始
// 的位置开始存放
for (unsigned i = 0; i < addedCount; i++)
newArray->lists[i] = addedLists[i];
// 释放原有的array()
free(array());
// 设置新的 newArray
setArray(newArray);
validate();
}
// 如果主类中有方法,第一次list主类中的方法
// 如果主类中没有方法,则进来的就是分类方法
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
validate();
}
// 当list是一维数组时,此时进入下面的判断创建`array_t`类型的结构体类型
// `array_t`的lists存放的是各个分类数组的地址指针
else {
// 1 list -> many lists
// 将list数组赋值给oldList
Ptr<List> oldList = list;
// oldList 存在 oldCount = 1
uint32_t oldCount = oldList ? 1 : 0;
// 新的newCount = 原有的count + 新增的count
uint32_t newCount = oldCount + addedCount;
// 根据`newCount`开辟内存,类型是 array_t, array()->lists是一个二维数组
setArray((array_t *)malloc(array_t::byteSize(newCount)));
// 设置数组的个数
array()->count = newCount;
// 将原来的list放在数组的末尾
if (oldList) array()->lists[addedCount] = oldList;
// 遍历addedLists将遍历的数据从数组的开始位置存储
for (unsigned i = 0; i < addedCount; i++)
array()->lists[i] = addedLists[i];
validate();
}
}
源码分析,attachLists总共分三步流程
-
0 list-->1 list- 将
addedLists[0]的指针赋值给list
- 将
-
1 list-->many lists- 计算旧的
list的个数 - 计算新的
list个数 ,新的list个数 = 原有的list个数+ 新增的list个数 - 根据
newCount开辟相应的内存,类型是array_t类型,并设置数组setArray - 将原有的
list放在数组的末尾,因为最多只有一个不需要遍历存储 - 遍历
addedLists将遍历的数据从数组的开始位置存储
- 计算旧的
-
many lists-->many lists- 判断
array()是否存在 - 计算原有的数组中的
list个数array()->lists的个数 - 新的
newCount= 原有的count+ 新增的count - 根据
newCount开辟相应的内存,类型是array_t类型 - 设置新数组的个数等于
newCount - 设置原有数组的个数等于
newCount - 遍历原有数组中
list将其存放在newArray->lists中 且是放在数组的末尾 - 遍历
addedLists将遍历的数据从数组的开始位置存储 - 释放原有的
array() - 设置新的
newArray
- 判断
注意:List* const * addedLists是二级指针
setArray和hasArray的探究
union {
Ptr<List> list;
uintptr_t arrayAndFlag;
};
bool hasArray() const {
// arrayAndFlag 的第 0 位是 1,则返回 ture
return arrayAndFlag & 1;
}
array_t *array() const {
// 将arrayAndFlag 的第 0 位置 0,返回
return (array_t *)(arrayAndFlag & ~1);
}
void setArray(array_t *array) {
// 将 arrayAndFlag 的第 0 位置 1
arrayAndFlag = (uintptr_t)array | 1;
}
attachLists 流程分析图:
1.3 验证 attachLists
验证attachLists绕不过attachCategories,前面只是简单介绍了attachCategories具体的细节没有探究,下面断点调试验证下
1.3.1 attachCategories 验证
创建 YJPerson 的分类 YJ:
@implementation YJPerson
+ (void)load {
NSLog(@"调用了 : %s", __func__);
}
- (void)say1 {
NSLog(@"调用了 : %s", __func__);
}
@end
@implementation YJPerson (YJ)
+ (void)load {
NSLog(@"调用了 : %s", __func__);
}
- (void)sayCate1 {
NSLog(@"调用了 : %s", __func__);
}
运行源码,进行调试:
图中 mlist 是分类 YJ 中的方法列表。此时打印下 mlists 中数据:
跳过 mlists[ATTACH_BUFSIZ - ++mcount] = mlist; 再次打印 mlists
再次打印 发现最后一个地址变成了 0x00000001000080f8,正好是前面的 mlist 地址;即 mlists 中最后一位存方的是YJ分类的方法列表的地址
mlists是一个二维指针类型
mlists + ATTACH_BUFSIZ - mcount就是地址偏移和前面探究的d+n是一样的。mlists是首地址,ATTACH_BUFSIZ - mcount具体的第几个位置
1.3.2 attachLists验证
1.3.2.1 0 对 1
前面我们知道在 extAllocIfNeeded 中返回 rwe,有值直接返回,没有就去创建
class_rw_ext_t *extAllocIfNeeded() {
auto v = get_ro_or_rwe(); // 判断rwe是否存在
if (fastpath(v.is<class_rw_ext_t *>())) { // 如果已经有rwe直接返回地址指针
return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
} else { // 为rwe开辟内存并且返回地址指针
return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
}
}
rwe不存在就会去开辟内存调用extAlloc方法
class_rw_ext_t *
class_rw_t::extAlloc(const class_ro_t *ro, bool deepCopy)
{
runtimeLock.assertLocked();
// 为rwe开辟内存
auto rwe = objc::zalloc<class_rw_ext_t>();
rwe->version = (ro->flags & RO_META) ? 7 : 0;
// 如果主类有方法,那么就会将主类的方法列表进行`attachLists`此时是 0 对 多
method_list_t *list = ro->baseMethods();
if (list) {
if (deepCopy) list = list->duplicate();
rwe->methods.attachLists(&list, 1);
}
//如果主类有属性,那么就会将主类的属性列表进行`attachLists`此时是0 对 多
property_list_t *proplist = ro->baseProperties;
if (proplist) {
rwe->properties.attachLists(&proplist, 1);
}
//如果主类有协议,那么就会将主类的协议列表进行`attachLists`此时是 0 对 多
protocol_list_t *protolist = ro->baseProtocols;
if (protolist) {
rwe->protocols.attachLists(&protolist, 1);
}
// 设置rwe
set_ro_or_rwe(rwe, ro);
// 返回rwe
return rwe;
}
源码显示如果主类中存在方法,那么就将方法进行 attachLists,如果主类中没有方法就此时什么都不做。此时就会出现两种情况:主类有方法和主类无方法
主类有方法/分类有方法
创建主类 YJPerson,分类 YJPerson+YJA 和 YJPerson+YJB 代码如下:
@implementation YJPerson
+ (void)load {
NSLog(@"调用了 : %s", __func__);
}
- (void)say1 {
NSLog(@"调用了 : %s", __func__);
}
@end
@implementation YJPerson (YJA)
+ (void)load {
NSLog(@"调用了 : %s", __func__);
}
- (void)sayCateA {
NSLog(@"调用了 : %s", __func__);
}
@end
@implementation YJPerson (YJB)
+ (void)load {
NSLog(@"调用了 : %s", __func__);
}
- (void)sayCateB {
NSLog(@"调用了 : %s", __func__);
}
@end
运行objc源码定位到 attachCategories 方法,因为现在研究的是分类
图中的第一个断点先断住,然后再进入第二个断点,目的是确定我们调试的是自定义的 YJPerson类。然后进入extAllocIfNeeded方法
此时rwe还没有值所以需要开辟内存走extAlloc方法
此时list有值,并且是主类的,所以调用attachLists方法,进入attachLists方法
- 断点进入
0对1的分支中 - 通过
lldb调试,确定是主类的方法列表
主类没有方法/分类有方法
将主类 YJPerson 中的 say1 移除,重复上面的步骤:
@implementation YJPerson
+ (void)load {
NSLog(@"调用了 : %s", __func__); }
}
@end
按照上面的调试流程,进入extAlloc方法,此时的list = NULL,所以不会调用attachLists方法,但是要想动态添加到主类0对1这个流程是必须要走的的,接着调试
attachCategories 的第二个参数是对category_t进行了一层包装,包装成locstamped_category_t类型,通过lldb调试里面分类是YJA分类,接着往下调试
此时会调用attachLists方法,进入attachLists方法
进入0对1流程的是YJA中的方法列表
总结
0对1流程有两种情况
主类有方法/分类有方法:以主类的方法为基础将分类的方法加载到主类中主类没有方法/分类有方法:以第一个编译的分类为基础将其它分类一起合并,最后加载到主类中
1.3.2.2 1 对 多
现在把还原 YJPerson 中的 say1方法,运行源码:
oldlist是主类中的方法列表,addedLists中存放着分类方法列表的指针,从addedLists取出分类中数据,现在看合并后的数据
array()->lists 中存放着两个方法列表的地址,分类的方法列表是是放在前面的
1.3.2.3 多 对 多
按照上面的逻辑继续执行,进入多对多的流程,继续进行断点调试
再次调用 addedLists,进去:
hasArray()=ture进入到多对多流程,addedLists二级指针中只有一个方法列表,是YJB分类的
array()的lists中存放3个方法列表,分别是分类YJB、YJA 和主类类 YJPerson的方法列表。最后编译的分类是在整个lists的最前面
二. 类和分类的组合
2.1 非懒类+非懒分类
主类 YJPerson 和分类 YJA 都实现load 方法:
前面我们知道分类的加载必然会调用 attachCategories(准备分类数据,然后调用 attachLists 添加到主类列表)方法,我们在 attachCategories 中加入断点,bt 查看调用栈,即可知。运行源码,输出日志:
总结:
-
非懒类加载流程
map_images-->map_images_nolock-->_read_images-->realizeClassWithoutSwift-->methodizeClass-->attachToClass
-
非懒分类加载流程
load_images-->loadAllCategories-->load_categories_nolock-->attachCategories-->attachLists
2.2 非懒类+懒分类
主类 YJPerson 实现 load 方法,分类 YJA 不实现load 方法:
运行源码:
没有调用 attachCategories 方法,直接就结束了。。。 既然没有动态加载,那么是不是在编译期呢,在加载类的时候,去获取ro中的方法列表,看看有没有分类的数据
ro中不仅有主类的方法,同时还有分类的方法。ro是在编译期就确定的
总结:
- 非懒类: 还是走的
map_images流程 - 懒分类
懒分类的数据在编译期就已经合并到了主类中,且分类的数据也是放在主类的方法前面
2.3 懒类+懒分类
主类 YJPerson 和分类 YJA 都不实现load 方法:
运行源码,输出:
直接走到了 mian 函数,继续下一步:
断在了 realizeClassWithoutSwift,查看调用栈信息
确实是我们调用的 alloc 方法
堆栈信息显示是通过 消息慢速查找流程 调用了realizeClassWithoutSwift方法。。。 有些信息明明之间关联起来了。。。
那么分类的数据是在什么时候加载的呢?很明显是在编译时。验证下
总结:
懒类的流程是第一次发消息的时会进行类的加载,懒分类的数据是在编译时就合并到ro中
2.4 懒类+非懒分类
2.4.1 懒类 + 一个非懒分类
主类不实现 load 方法,分类实现 load 方法
运行源码,输出:
这个不是和 2.2 非懒类+懒分类 一样么?断住类的加载,打印 ro 看一下:
果然和 2.2 非懒类+懒分类 一样,非懒分类 强制把 懒类 按 非懒类来加载,然后将 非懒分类的数据合并到了主类中
总结: 参考2.2 非懒类+懒分类
2.4.2 懒类+多个非懒分类
主类不实现 load 方法,分类 YJA、YJB 实现 load 方法
运行源码,输出:
打印的信息分析:主类的加载没有走map_images流程,调用两次load_categories_nolock说明是有两个分类,但是最后没有走attachCategories方法,而是走realizeClassWithoutSwift加载主类,然后调用attachCategories流程。在整个流程中需要解决两个问题
- 分类加载过程中没有走
attachCategories方法,那么它的流程是什么? - 怎么调用到
realizeClassWithoutSwift流程的
在load_categories_nolock添加断点,运行源码
cls如果初始化则走attachCategories方法,如果没有则走unattachedCategories.addForClass方法。进入addForClass方法
void addForClass(locstamped_category_t lc, Class cls)
{
runtimeLock.assertLocked();
if (slowpath(PrintConnecting)) {
_objc_inform("CLASS: found category %c%s(%s)",
cls->isMetaClassMaybeUnrealized() ? '+' : '-',
cls->nameForLogging(), lc.cat->name);
}
// 先到哈希表中的去查找又没有lc
auto result = get().try_emplace(cls, lc);
// 如果有 判断result.second 是否有数据,没有将lc存入result.second
if (!result.second) {
result.first->second.append(lc);
}
}
- 首先到哈希表中根据
key(cls)是查找lc,lc是系统底层统一封装的数据类型 - 如果表中有
lc,判断lc.second是否有数据,如果没有则赋值 - 注意现在的分类数据直接存在哈希表中和类现在没有关系。这点很重要
再探究下怎么去加载类的在realizeClassWithoutSwift添加断点,运行源码
堆栈信息显示是load_images --> prepare_load_methods -->realizeClassWithoutSwift 探究下prepare_load_methods的具体实现
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertLocked();
// 从macho中获取类的非懒加载列表
classref_t const *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
// 将重新映射的类添加到load列表中
schedule_class_load(remapClass(classlist[i]));
}
// 从macho中获取非懒加载类的列表
category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
Class cls = remapClass(cat->cls);
if (!cls) continue; // category for ignored weak-linked class
if (cls->isSwiftStable()) {
_objc_fatal("Swift class extensions and categories on Swift "
"classes are not allowed to have +load methods");
}
// 类的加载
realizeClassWithoutSwift(cls, nil);
ASSERT(cls->ISA()->isRealized());
// 将分类添加到分类的load列表中
add_category_to_loadable_list(cat);
}
}
macho中获取非懒加载类的列表- 将重新映射的类添加到类的
load列表中 macho中获取非懒加载分类列表- 类的初始化加载
- 将分类添加到分类的
load列表中
探究下schedule_class_load
static void schedule_class_load(Class cls)
{
if (!cls) return;
ASSERT(cls->isRealized()); // _read_images should realize
if (cls->data()->flags & RW_LOADED) return;
// 递归`cls`的父类
// Ensure superclass-first ordering
schedule_class_load(cls->getSuperclass());
// 将类还有父类都添加到load表中
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED); `IMP`
}
add_category_to_loadable_list和 add_class_to_loadable_list基本一样
{
...// 省略部分代码
// 获取类中load方法的IMP
method = cls->getLoadMethod();
// 类的load表
loadable_classes[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
loadable_classes_used++;
...
...
// 获取分类中load方法的IMP
method = _category_getLoadMethod(cat);
// 分类的load表
loadable_categories[loadable_categories_used].cat = cat;
loadable_categories[loadable_categories_used].method = method;
loadable_categories_used++;
...
}
add_category_to_loadable_list和add_class_to_loadable_list保存的是封装的类型,这个类型有两个变量一个是cls保存类,一个是method保存的是load方法的IMP
探究realizeClassWithoutSwift后续的流程,根据打印的消息是流程是realizeClassWithoutSwift --> attachToClass --> attachCategories
void attachToClass(Class cls, Class previously, int flags)
{
runtimeLock.assertLocked();
ASSERT((flags & ATTACH_CLASS) ||
(flags & ATTACH_METACLASS) ||
(flags & ATTACH_CLASS_AND_METACLASS));
auto &map = get();
// 在哈希表中key = previously(cls)对应的一张表
// 这张表中存放者分类的数据
auto it = map.find(previously);
// 如果it和表中的最后一个数据不相等进入判断,最后一个算是标识位
if (it != map.end()) {
// 获取分类的数据list的地址
category_list &list = it->second;
if (flags & ATTACH_CLASS_AND_METACLASS) {
// 给元类类加载分类数据
int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS;
attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS);
attachCategories(cls->ISA(), list.array(), list.count(), otherFlags | ATTACH_METACLASS);
} else {
// 给类加载分类数据
attachCategories(cls, list.array(), list.count(), flags);
}
// 释放表中cls对应表的数据
map.erase(it);
}
在attachToClass中断点调试
it != map.end()成立进入判断流程,只要哈希表中存分类的数据条件就成立,下面探究下it中到底在哪里存了分类的数据
分类的数据存在了底层的表中,当需要把分类的数据加载到主类的时候,就从表中获取,加载完以后清空对应表中分类的数据