前言
前面已经探究了类的加载,类的加载分为懒加载类和非懒加载类,他们有不同加载流程,下面来探究下分类的加载,以及分类和主类之间加载不同的情况
准备工作
- objc-818.2
- MachOView 工具
分类的加载
分类的底层结构是结构体categor_t
,分类一般是加载到主类中的,什么时候加载以及加载的过程,这是下面要探究的
分类加载引入
WWDC
类的优化中苹果为分类和动态添加专门分配的了一块内存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
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
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
方法
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
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
-->load_categories_nolock
-->attachLists
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
是二级指针
。 就像LWPerson * p
=[LWPerson alloc]
,p
叫做一级指针
,&p
就叫二级指针
setArray
和hasArray
的探究
union {
Ptr<List> list;
uintptr_t arrayAndFlag;
};
bool hasArray() const {
return arrayAndFlag & 1;
}
array_t *array() const {
return (array_t *)(arrayAndFlag & ~1);
}
void setArray(array_t *array) {
arrayAndFlag = (uintptr_t)array | 1;
}
void validate() {
for (auto cursor = beginLists(), end = endLists(); cursor != end; cursor++)
cursor->validate();
}
setArray()
方法arrayAndFlag
=(uintptr_t)array
|1
。array
异或1
,arrayAndFlag
的第0
位一定是1
hasArray()
方法arrayAndFlag
&1
。如果arrayAndFlag
的第0
位是1
,就返回YES
,否则返回NO
- 所以只要调用
setArray()
方法,不释放。hasArray()
就是YES
,和上面的3
个流程对应起来
attachLists
流程分析图
实例验证 attachLists
实例验证attachLists
不可能绕过attachCategories
,前面只是简单介绍了attachCategories
具体的细节没有探究,下面断点调试验证下
验证 attachCategories
创建主类LWPerson
和分类LWA
@implementation LWPerson
+(void)load{
NSLog(@"我是主类LWPerson");
}
-(void)sayHello{
NSLog(@"sayHelloP");
}
@end
@implementation LWPerson (LWA)
+(void)load{
NSLog(@"我是分类LWA");
}
- (void)AsayNB{
NSLog(@"我来了");
}
@end
运行源码,进行调试
图中显示加载的是LWA
分类的方法列表,看下mlists
的结构
mlists
中最后一位存方的是LWA
分类的方法列表的地址,mlists
是一个二维指针类型
mlists + ATTACH_BUFSIZ - mcount
就是地址偏移和前面探究的d
+n
是一样的。mlists
是首地址,ATTACH_BUFSIZ - mcount
具体的第几个位置
验证attachLists
实例验证准备用 查看macho
+ 动态调试
进行相互验证,在验证之前先补充下怎么读取macho
文件
的,源码中出现_getObjc2ClassList
、_getObjc2NonlazyClassList
等方法,进入方法看看具体实现
#define GETSECT(name, type, sectname) \
type *name(const headerType *mhdr, size_t *outCount) {
return getDataSection<type>(mhdr, sectname, nil, outCount); \
} \
type *name(const header_info *hi, size_t *outCount) { \
return getDataSection<type>(hi->mhdr(), sectname, nil, outCount); \
}
// function name content type section name
//refs结尾的都是需要修复的类和方法等
GETSECT(_getObjc2SelectorRefs, **SEL**, "__objc_selrefs");
GETSECT(_getObjc2MessageRefs, message_ref_t, "__objc_msgrefs");
GETSECT(_getObjc2ClassRefs, Class, "__objc_classrefs");
GETSECT(_getObjc2SuperRefs, Class, "__objc_superrefs");
//macho section 等于__objc_classlist 所有类的列表(不包括分类)
GETSECT(_getObjc2ClassList, classref_t **const**, "__objc_classlist");
//macho section 等于__objc_nlclslist 懒加载类的列表
GETSECT(_getObjc2NonlazyClassList, classref_t **const**, "__objc_nlclslist");
//macho section 等于__objc_catlist 分类的列表
GETSECT(_getObjc2CategoryList, category_t * **const**, "__objc_catlist");
//macho section 等于__objc_catlist2 分类的列表
GETSECT(_getObjc2CategoryList2, category_t * **const**, "__objc_catlist2");
//macho section 等于__objc_nlcatlist 懒加载分类
GETSECT(_getObjc2NonlazyCategoryList, category_t * **const**, "__objc_nlcatlist");
//macho section 等于__objc_protolist 协议列表
GETSECT(_getObjc2ProtocolList, protocol_t * **const**, "__objc_protolist");
//macho section 等于__objc_protolist 协议修复列表
GETSECT(_getObjc2ProtocolRefs, protocol_t *, "__objc_protorefs");
//macho section 等于__objc_init_func __objc_init初始化方法列表
GETSECT(getLibobjcInitializers, UnsignedInitializer, "__objc_init_func");
__objc_nlclslist
对应macho
文件(准确的说是可执行文件)的section
的名字
macho
文件左边section
的名称对应着右边数据的列表,图中应该很清晰了
0
对 1
如果rwe
有值肯定会调用extAllocIfNeeded
方法,调用extAllocIfNeeded
的时机,动态添加数据或分类的数据动态添加到主类,现在研究分类的情况。进入extAllocIfNeeded
方法
class_rw_ext_t *extAllocIfNeeded() {
auto v = get_ro_or_rwe();
if (fastpath(v.is<class_rw_ext_t *>())) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
} else {
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
,如果主类中没有方法就此时什么都不做。此时就会出现两种情况主类有方法和主类无方法
主类有方法,分类有方法
创建主类LWPerson
和LWTeacher
,创建分类LWPerson+LWB
和 LWPerson+LWA
代码如下
@implementation LWTeacher
- (void)sayHello{
NSLog(@"sayHelloT");
}
@end
@implementation LWPerson
+(void)load{
NSLog(@"我是主类LWPerson");
}
-(void)sayHello{
NSLog(@"sayHelloP");
}
@end
@implementation LWPerson (LWB)
+(void)load{
NSLog(@"我是分类LWB");
}
- (void)BsayHello{
NSLog(@"我来了");
}
@end
@implementation LWPerson (LWA)
+(void)load{
NSLog(@"我是分类LWA");
}
- (void)AsayNB{
NSLog(@"我来了");
}
@end
运行objc
源码定位到attachCategories
方法,因为现在研究的是分类
图中的第一个断点先断住然后在进入第二个断点,目的是过滤其它类。然后进入extAllocIfNeeded
方法
此时rwe
还没有值所以需要开辟内存走extAlloc
方法
此时list
有值,所以调用attachLists
方法,进入attachLists
方法
- 断点进入
0
对1
的判断中 - 通过
lldb
调试,是主类的方法列表
主类没有方法,分类有方法
将sayHello
方法从LWPerson
移除,重复上面的调试步骤
@implementation LWPerson
+(void)load{
NSLog(@"我是主类LWPerson");
}
@end
按照上面的调试流程,进入extAlloc
方法,此时的list
= NULL
,所以不会调用attachLists
方法,但是要想动态添加到主类0
对1
这个流程流程一定要进入的,接着调试
attachCategories
的第二个参数是对分类进行了一层包装,包装成locstamped_category_t
类型
通过lldb
调试里面分类是LWA
分类,接着往下调试
此时会调用attachLists
方法,进入attachLists
方法
进入0
对1
流程的是LWA
中的方法列表,大家可能奇怪为啥是加载LWA
中的方法列表而不是LWB
中
这个和分类的编译顺序有关,按编译的先后顺序进行加载
如果把分类
LWB
放在分类LWA
前面先编译那么LWB
就会先加载
总结
0
对1
流程有两种情况
- 主类有方法,分类有方法:以主类的方法为基础将分类的方法加载到主类中
- 主类没有方法,分类有方法:以第一个编译的分类为基础将其它分类一起合并,最后加载到主类中
1
对 多
现在把sayHello
方法添加到LWPerson
中,运行源码
把分类LWB
放在分类LWA
前面先编译那么LWB
就会先加载,进入attachLists
方法
list
是主类中的方法列表,addedLists
中存放着分类的指针,从addedLists
取出分类中数据,现在看合并后的数据
array()->lists
中存放着两个方法列表的地址, 分类的方法列表是是放在前面的
多
对 多
按照逻辑下一步要进入多
对多
的流程,继续进行断点调试
此时加载的是LWA
分类,进入attachLists
方法
hasArray()
=ture
进入到多对多流程,addedLists
二级指针中只有一个方法列表是LWA
分类的
newArray
的lists
中存放3
个方法列表,分别是分类LWA
的方法列表,分别是分类LWB
的方法列表以及主类的方法列表。最后编译的分类是在整个lists
的最前面
类和分类的搭配
非懒加载类 + 非懒加载分类
非懒加载类的加载流程
非懒加载类实现load
,非懒加载分类实现load
。非懒加载类的数据加载是通过_getObjc2NonlazyClassList
方法从macho
文件获取,非懒加载分类的数据加载是通过_getObjc2NonlazyCategoryList
方法从macho
文件获取
非懒加载类获取数据示意图
非懒加载分类获取数据示意图
- 非懒加载类加载流程:
map_images
-->map_images_nolock
-->_read_images
-->realizeClassWithoutSwift
-->methodizeClass
-->attachToClass
- 非懒加载分类加载流程:
load_images
-->loadAllCategories
-->load_categories_nolock
-->attachCategories
-->attachLists
日志打印示意图
非懒加载类 + 懒加载分类
非懒加载类
还是走map_images
流程- 懒加载分类没有走
attachCategories
,那么分类中方法列表是什么时候加载的呢
macho
中分类的列表是没有数据的,那就说明不可能是动态时加载分类的数据,那么到底在什么时间去加载分类的数据呢
猜想:不在动态是添加,那么最有可能是在编译期,在加载类的时候,去获取ro
中的方法列表,看看有没有分类的数据
ro
中不仅有主类的方法,同时还有分类的方法。ro
是在编译期就确定的,也就是说懒加载分类中的数据在编译期就已经合并到了主类中。而且分类的数据也是放在主类的方法前面
懒加载类 + 懒加载分类
打印信息显示加载类没有走map_images
流程,表示没有LWPeson
没有在非懒加载列表中
macho
中没有非懒加载类的数据列表和非懒加载分类的数据列表,那么类是在什么时候加载的呢?在realizeClassWithoutSwift
添加断点,查看堆栈信息
堆栈信息显示是通过消息慢速查找流程调用了realizeClassWithoutSwift
方法,一切就是那么的有缘,有些信息明明之间关联起来了,这就是缘分
那么分类的数据是在什么时候加载的呢?很明显是在编译时。验证下
懒加载类的流程是第一次发消息的时会进行类的加载,而懒加载分类的数据是在编译时就合并到ro
中
懒加载类 + 非懒加载分类
这种情况流程比较复杂,因为非懒加载分类的个数是对整个加载流程是有影响的
懒加载类 + 一个非懒加载分类
这种方式和非懒加载类
+ 懒加载分类
是一样的,非懒加载分类强制把懒加载类,加载提前到非懒加载类加载的时候,而且编译时也是把懒加载类变成了非懒加载类,然后非懒加载的分类的数据合并到了主类中
macho
文件数据展示已经很明显了,下面看下是不是在分类合并在ro
中
懒加载类变成非懒加载类,分类的数据在编译期间合并到ro
中
懒加载类 + 多个非懒加载分类
打印的信息分析:主类的加载没有走
map_images
流程,调用两次load_categories_nolock
说明是有两个分类,但是最后没有走attachCategories
方法,而是走realizeClassWithoutSwift
加载主类,然后调用attachCategories
流程。在整个流程中需要解决两个问题
- 分类加载过程中没有走
attachCategories
方法,那么它的流程是什么 - 怎么调用到
realizeClassWithoutSwift
流程的
macho
中分类列表和非懒加载分了列表,有LWB
分类和LWA
分类,但是没有非懒加载类列表
在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
中到底在哪里存了分类的数据
分类的数据存在了底层的表中,当需要把分类的数据加载到主类的时候,就从表中获取,加载完以后清空对应表中分类的数据
总结
类的加载很复杂很复杂每个地方单独拿出来都能写一篇博客,现在只能整理下整体的流程。底层的探索任然在继续,这就是痛并快乐着