前言
接上篇 类的加载之read_images分析,在上篇探索了map_images
→ _read_images
→ readClass
→ addNamedClass
→ addClassTableEntry
的流程。
其中,当加载非懒加载类realize non-lazy classes
时,首先将类插入到表addClassTableEntry
,其次调用realizeClassWithoutSwift
用于初始化类的操作。
realizeClassWithoutSwift
主要流程有:rw和ro赋值(占位)
→ 父类和元类加载及关联
→ 设置成员变量
→ 设置标记位
→ methodizeClass赋值(rw和ro真正赋值)
。
rw 和 ro 赋值
// 从类中获取ro
auto ro = (const class_ro_t *)cls->data();
auto isMeta = ro->flags & RO_META;
// 判断rw是否已经分配 (future class)
if (ro->flags & RO_FUTURE) {
// 从 future class 获取ro
rw = cls->data();
ro = cls->data()->ro();
ASSERT(!isMeta);
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// 如果是正常的类
rw = objc::zalloc<class_rw_t>(); // 开辟内存
rw->set_ro(ro); // 设置ro
rw->flags = RW_REALIZED|RW_REALIZING|isMeta; // 设置标记
cls->setData(rw); // 设置类的rw
}
复制代码
说明:
- 通过
cls->data()
获取ro
数据。rw
开辟空间,将ro
赋值给rw
的ro()
中。(苹果对此做了对应的优化。具体可以参考WWDC-关于 runtime 的优化)- 设置
rw
的flags
,其中RW_REALIZED
(31位
)表示rw
是否已经初始化完毕;RW_REALIZING
(19位
)表示rw
是否初始化中;isMeta
(0位
)是否是元类。- 赋值
rw
给cls
。
但是,经过实际调试,在执行完该流程后,baseMethodList
为空。
说明此处
并不是
真正意义上的赋值,而是根据类的offset
情况去占用对应的位置。
父类和元类加载及关联
父类
和元类
加载
// 递归加载父类、元类
supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
复制代码
递归实例化
父类
和元类
ISA
纯指针处理
#if SUPPORT_NONPOINTER_ISA
if (isMeta) {
// 如果是元类,其ISA是纯指针的情况
// 设置原始的ISA到缓存
cls->setInstancesRequireRawIsa();
} else {
bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
bool rawIsaIsInherited = false;
static bool hackedDispatch = false;
// 对应环境变量中的 OBJC_DISABLE_NONPOINTER_ISA配置
if (DisableNonpointerIsa) {
// 如果环境变量true后,isa是一个纯指针。
instancesRequireRawIsa = true;
}
// 如果是os_object类时纯指针
else if (!hackedDispatch && 0 == strcmp(ro->getName(), "OS_object"))
{
hackedDispatch = true;
instancesRequireRawIsa = true;
}
// 如果父类是纯指针,并且父类的父类存在。
else if (supercls && supercls->getSuperclass() &&
supercls->instancesRequireRawIsa())
{
instancesRequireRawIsa = true;
// rawIsaIsInherited 表示继承的是纯指针
rawIsaIsInherited = true;
}
// 如果纯指针标记位true
if (instancesRequireRawIsa) {
// 递归设置类和子类的isa为纯指针,此处 rawIsaIsInherited 为打印标记位
cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
}
}
// SUPPORT_NONPOINTER_ISA
#endif
复制代码
- 判断
isa
是否为纯指针。- 判断
os_object
、父类
等纯指针
,是否需要递归设置纯指针。- 递归设置
类
和子类
的isa
为纯指针。
父类
和元类
关联
// 关联父类与元类。
cls->setSuperclass(supercls);
cls->initClassIsa(metacls);
复制代码
关联
父类
与元类
。对应类的继承链
与isa走位图
。
设置成员变量及标记位
// 修正 ivar 的 offset,可能会重新创建 class_ro_t 来更新 ivar
if (supercls && !isMeta) reconcileInstanceVariables(cls, supercls, ro);
// 设置实例大小(成员变量的大小)
cls->setInstanceSize(ro->instanceSize);
// 同步标记位
if (ro->flags & RO_HAS_CXX_STRUCTORS) {
cls->setHasCxxDtor();
if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
cls->setHasCxxCtor();
}
}
复制代码
- 在有父类的情况下,并且非元类会修正
ivar
的offset
- 重新设置
成员变量
的大小。
methodizeClass
上面rw
和ro
赋值过程中,通过实际测试并没有方法数据(baseMethodList
为空),这是因为此时的rw
只是一个空的结构的地址,其中sel
和imp
还没有关联起来。
static void methodizeClass(Class cls, Class previously)
{
bool isMeta = cls->isMetaClass();
auto rw = cls->data();
auto ro = rw->ro();
auto rwe = rw->ext();
// 加载自身的方法和属性到rwe中。
method_list_t *list = ro->baseMethods();
if (list) {
// 准备并修正(排序)方法列表
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
if (rwe) rwe->methods.attachLists(&list, 1);
}
// 加载属性到rwe
property_list_t *proplist = ro->baseProperties;
if (rwe && proplist) {
rwe->properties.attachLists(&proplist, 1);
}
// 加载协议到rwe
protocol_list_t *protolist = ro->baseProtocols;
if (rwe && protolist) {
rwe->protocols.attachLists(&protolist, 1);
}
// 如果是根元类
if (cls->isRootMetaclass()) {
// 添加initialize方法
addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
}
// 添加当前类的方法到 unattachedCategories 表里
objc::unattachedCategories.attachToClass(cls, cls,
isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
}
复制代码
- 添加
cls
的方法和属性到rwe
中。- 根元类判断,添加
initialize
方法。- 将加载好的类添加到
unattachedCategories
表中。
rw 和 ro 数据验证
1. 当执行验证时,发现list
为空。
原因是此时的
ro
其实是元类的ro
,元类中是没有方法的,需要对元类进行过滤
。
2. 再次验证
终端查看方法
注意:
- 如果是
第一次
加载cls
是没有任何方法修正
,此处不会
打印出方法。这里是因为在之前调试的时候已经存在
缓存,所以可以打印出方法。- 如果没修正方法,会执行
prepareMethodLists
进行方法修正。
prepareMethodLists
static void prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
bool baseMethods, bool methodsFromBundle, const char *why)
{
// 核心代码
for (int i = 0; i < addedCount; i++) {
method_list_t *mlist = addedLists[i];
if (!mlist->isFixedUp()) {
// 修正方法(排序)
fixupMethodList(mlist, methodsFromBundle, true/*sort*/);
}
}
}
复制代码
- 其中
addedCount
传值为1
,addedLists[0]
为ro
的list
。- 如果没有修正,则执行
fixupMethodList
进行排序。
fixupMethodList
static void fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
// dyld3可能已经标记唯一了,但没有排序
if (!mlist->isUniqued()) {
// 遍历方法列表
for (auto& meth : *mlist) {
const char *name = sel_cname(meth.name());
// 获取sel的名字,并设置。
meth.setName(sel_registerNameNoLock(name, bundleCopy));
}
}
// 排序方法表(对sel进行地址排序)
if (sort && !mlist->isSmallList() && mlist->entsize() == method_t::bigSize) {
method_t::SortBySELAddress sorter;
std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter);
}
}
复制代码
根据
name
对SEL
进行了修正,对应慢速消息查找的二分查找。
排序验证
在上面执行stable_sort
前后,添加如下代码分别打印排序前后的方法情况。
for (auto& meth : *mlist) {
const char *name = sel_cname(meth.name());
// 打印方法名
printf("排序后:%s -- %p\n", name, meth.name());
}
复制代码
结果如下:
排序前:subInstanceMethod1 -- 0x100003a16
排序前:subInstanceMethod2 -- 0x100003a29
排序前:subInstanceMethod3 -- 0x100003a3c
排序前:subInstanceMethod4 -- 0x100003a4f
排序前:subInstanceMethod5 -- 0x100003a62
排序前:init -- 0x100003906
--------------
排序后:subInstanceMethod1 -- 0x100003a16
排序后:subInstanceMethod2 -- 0x100003a29
排序后:subInstanceMethod3 -- 0x100003a3c
排序后:subInstanceMethod4 -- 0x100003a4f
排序后:subInstanceMethod5 -- 0x100003a62
排序后:init -- 0x7fff7b905a35
复制代码
- 排序前和排序后的顺序是一样的,因为可能之前已经修正过了,或者是添加的时候就已经是正确的顺序。
init
分配了正确的地址。
分类的探究
分类结构
创建如下分类,并查看分类结构。
@interface ZLSubObject (ZLExtend)
@property (nonatomic, strong, nullable) NSString *cate_name;
- (void)cate_instanceMethod;
+ (void)cate_classMethod;
@end
@implementation ZLSubObject (ZLExtend)
- (void)cate_instanceMethod {
NSLog(@"%s",__func__);
}
+ (void)cate_classMethod {
NSLog(@"%s",__func__);
}
@end
复制代码
clang
查看
struct _category_t {
const char *name;
struct _class_t *cls;
const struct _method_list_t *instance_methods;
const struct _method_list_t *class_methods;
const struct _protocol_list_t *protocols;
const struct _prop_list_t *properties;
};
复制代码
源码
查看
struct category_t {
const char *name; // 分类名
classref_t cls; // 所属类
WrappedPtr<method_list_t, PtrauthStrip> instanceMethods; // 属性方法列表
WrappedPtr<method_list_t, PtrauthStrip> classMethods; // 类方法列表
struct protocol_list_t *protocols; // 协议
struct property_list_t *instanceProperties; // 属性列表
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;
}
};
复制代码
rwe的赋值流程
在上面执行 methodizeClass
加载类时,获取了rwe
的值,但是通过打印,rwe
为空。这是因为rwe
在有分类的情况下会有值(出自:WWDC)。rwe
的赋值是来源于rw->ext()
函数。如下:
class_rw_ext_t *ext() const {
// get_ro_or_rwe()获取rwe的内容
return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);
}
复制代码
利用反推法,全局查找get_ro_or_rwe()
,只有extAllocIfNeeded()
获取的是rwe
本身。
class_rw_ext_t *extAllocIfNeeded() {
// 获取rwe
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 {
// 创建rwe
return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
}
}
复制代码
该方法判断了
rwe
是否存在,如果存在则获取并返回
,如果不存在,则创建rwe
全局搜索 extAllocIfNeeded
,只有attachCategories
调用最符合。
而attachCategories
的调用逻辑在attachToClass
与load_categories_nolock
中。
总结
由此分类的加载,从目前看有了三条路径:
realizeClassWithoutSwift
→methodizeClass
→attachToClass
→attachCategories
load_images
→loadAllCategories
→load_categories_nolock
→attachCategories
_read_images
→load_categories_nolock
→attachCategories
注意: 其中第三条,该流程的先决条件的执行了
load_images
之后才会执行。仅对于启动时出现的类别,将推迟到调用_dyld_objc_notify_register
的第一次load_images
方法之后。
*
分类加载的具体流程,后面再探究。