目录
一、前言
上一篇文章iOS 懒加载类与非懒加载类(类的加载中)中我们分析了懒加载类与非懒加载类的加载流程,并分析了分类的加载原理。我们知道了什么时候添加分类,什么时候初始化rwe
,但是什么时候将分类添加到类中不知道。那么这篇文章我们就继续往下探索。
二、readClass方法调用后类的结构探索
readClass- 可以看到进入
readClass
方法时cls
中并没有bits
数据。
- 进入
realizeClassWithoutSwift
方法后cls
中也没有bits
数据。
但是可以发现代码中是可以读取到cls->data();
数据,其中data
就是从bits
中获取的。那为什么lldb
中没有读取到bits
数据呢?
因为
lldb
中的x/4gx cls
是从内存中读取cls
的数据,但此时类的数据空间还没在内存中分配完毕,bits的初始化和赋值还没执行。而代码中却是从macho中通过指针地址读取类的bits
数据,类是唯一的,编译器在编译期就将类的指针分配完毕,每次运行类的指针都是相同的。
通过地址指针读取bits
数据:
setInstancesRequireRawIsaRecursively
方法前bits
为空
setInstancesRequireRawIsaRecursively
方法后bit
添加了前面的数据
setInstancesRequireRawIsaRecursively
方法注释为:将这个类及其所有子类标记为需要原始isa指针
setInstanceSize
方法前bits
还没有后面的数据:
setInstanceSize
方法后bits
有了后面的数据:
setHasCxxDtor
方法继续往bits
中添加数据:
三、将分类中的方法添加到类中
前提:将主类及分类的实现文件中均加入+load
实现,使之都变为非懒加载类
在上篇文章中我们知道了rwe
是在attachCategories
方法调用如下代码进行初始化的:
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
int flags)
{
...
auto rwe = cls->data()->extAllocIfNeeded();
...
}
而extAllocIfNeeded
方法是调用extAlloc
- 初始化
rwe
过程中将主类LGPerson
中的所有方法添加进了rwe
attachCategories
方法中初始化rwe
完成后回到attachCategories
方法
在这里添加分类中的方法到rwe
中,第二个分类重复这个步骤添加方法到rwe
中。
-
attachLists
方法中后添加分类方法列表
时会先开辟一块内存,将分类
的方法放在新列表的前面
,之前的方法列表通过内存平移
放到新列表的后面
。所以分类
和主类
有同名方法时会调用分类
中的方法。
所以分类的加载流程如下:
四、懒加载与非懒加载下分类的加载情况
-
主类为
非懒加载类
(+load
)、分类一为非懒加载分类
(+load
)、分类二为懒加载分类
只要分类中有一个为非懒加载分类
那么所有分类均会标记为非懒加载分类
从load_images
方法开始获取rwe
数据 -
主类为
懒加载类
、分类均为懒加载分类
第一次消息发送的时候加载类信息,rwe
数据从macho
中cls
的data()
获取 -
主类为
非懒加载类
(+load
)、分类均为懒加载分类
rwe
数据从macho
中cls
的data()
获取 -
主类为
懒加载类
、分类均为非懒加载分类
(+load
)
分类迫使主类
变为非懒加载类样式
来提前加载数据
五、LLVM相关流程分析
在类的加载过程中可以直接从macho
中读取内存地址并能获取class_ro_t
格式的数据,那是什么时候通过内存地址生成class_ro_t
格式的数据呢?
class_ro_t
数据结构:
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
// This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
_objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];
_objc_swiftMetadataInitializer swiftMetadataInitializer() const {
...
}
method_list_t *baseMethods() const {
return baseMethodList;
}
class_ro_t *duplicate() const {
...
}
};
由于macho
文件是在编译期生成的,所以生成class_ro_t
格式数据的时期也在编译期通过LLVM
生成的。LLVM源码地址
LLVM
下的class_ro_t
数据结构:
通过Read
方法的源码可知class_ro_t
中的数据在Read
方法中赋值,找到Read
方法的调用地址就能知道在哪个方法进行了class_ro_t
格式数据的生成。
网友评论