Objective-C 类的加载原理(中)
上篇文章分析了 _objc_init与read_images 的逻辑,最后定位到了类的初始化是在realizeClassWithoutSwift
中的,这篇文章将继续分析。
一、 realizeClassWithoutSwift
在realizeClassWithoutSwift
中发现了对ro
、rw
等的一系列操作。在read_imags
中要进入这个方法需要实现+ load
方法。
核心逻辑精简后如下:
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
class_rw_t *rw;
Class supercls;
Class metacls;
……
1.生成rw数据逻辑
//cache的初始化
cls->cache.initializeToEmptyOrPreoptimizedInDisguise();
#if FAST_CACHE_META
//是否元类
if (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif
// Choose an index for this class.
// Sets cls->instancesRequireRawIsa if indexes no more indexes are available
//为32位设计的。objc_indexed_classes_count 记录类的数量 最终存入objc_indexed_classes中(其中记录了index-cls的关系)。为isa是否纯指针做的处理。
cls->chooseClassArrayIndex();
2.关联元类与父类
3.调整ivars
4.同步flags标志位
5.关联子类与相邻类
// Attach categories
//分类处理
methodizeClass(cls, previously);
return cls;
}
- 通过
ro
生成rw
数据。 -
isa
的判断处理,关联元类与父类。这里会递归父类和元类的realizeClassWithoutSwift
,最后与cls
关联。 - 调整
ivars
的offset
。 - 同步
flags
的标志位给rw
(实际上是从ro
中存储到缓存中)。 - 关联子类与相邻的类。
- 分类的处理逻辑在
methodizeClass
中,将单独分析。
1.1、生成rw数据
auto ro = (const class_ro_t *)cls->data();
auto isMeta = ro->flags & RO_META;
//调试代码 开始
const char *mangledName = cls->nonlazyMangledName();
if (strcmp(mangledName, "HPObject") == 0 && !isMeta) {
printf("%s %s\n",__func__,mangledName);
}
//调试代码 结束
if (ro->flags & RO_FUTURE) {
// This was a future class. rw data is already allocated.
rw = cls->data();
ro = cls->data()->ro();
ASSERT(!isMeta);
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// Normal class. Allocate writeable class data.
//开辟rw空间
rw = objc::zalloc<class_rw_t>();
//将ro数据存入rw中。这也是为什么从data()->ro()的原因。
rw->set_ro(ro);
//flags标志位设置 uint32_t 1<<31 1<<19 1<<0 (元类为1,非元类为0)
rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
//设置rw数据,这个时候data()拿出来的就是rw数据了。
cls->setData(rw);
}
- 首先通过
cls->data()
获取ro
数据,在之前的class
结构探索的时候data()
获取的是rw
数据。这是因为rw
还没有赋值,从macho
中读取__objc_classlist
就存在了data()
中了。具体可以在赋值前后验证cls->data()
的地址。 -
rw
开辟空间,将ro
链接到rw
的ro()
中。(这里只是将地址给了rw
,并没有完全复制一份,苹果对这块做了对应的优化。具体可以参考OC 类探索(二) 或者观看WWDC2020-10163) - 设置
rw
的flags
是一个uint32_t
类型,31
位表示rw
是否已经初始化完毕(RW_REALIZED
),19
位表示rw
是否初始化中(RW_REALIZING
),0
位表示是否是元类(元类为1
,非元类为0
)。 - 最后将
rw
存进data
中。
⚠️这里有个细节是调试代码中判断了
image.pngisMeta
,因为元类和类的名称相同。否则元类也会进入这块逻辑。
ro
数据是在llvm
编译期就已经生成了。class_ro_t
在llvm
中的结构:
read
函数实现:
image.png
可以看到是直接进行的macho
文件的操作,直接进行的赋值。调用方是Read_class_row
:
image.png
验证ro
数据(类加载进内存就有了):
(lldb) p ro
(const class_ro_t *) $0 = 0x00000001000080a8
(lldb) p *$0
(const class_ro_t) $1 = {
flags = 128
instanceStart = 8
instanceSize = 8
reserved = 0
= {
ivarLayout = 0x0000000000000000
nonMetaclass = nil
}
name = {
std::__1::atomic<const char *> = "HPObject" {
Value = 0x0000000100003f2e "HPObject"
}
}
baseMethodList = 0x00000001000080f0
baseProtocols = 0x0000000000000000
ivars = 0x0000000000000000
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000000000000
_swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $1.baseMethodList
(void *const) $2 = 0x00000001000080f0
(lldb) p *$2
(lldb) p $1.baseMethods()
(method_list_t *) $3 = 0x00000001000080f0
(lldb) p *$3
(method_list_t) $4 = {
entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 1)
}
(lldb) p $4.get(0).big()
(method_t::big) $5 = {
name = "instanceMethod"
types = 0x0000000100003f57 "v16@0:8"
imp = 0x0000000100003ea0 (HPObjcTest`-[HPObject instanceMethod])
}
- 通过打印
ro
地址定位到了baseMethodList
。 - 打印
baseMethodList
并没有显示任何数据,可以通过baseMethods()
方法或者将baseMethodList
强转成method_list_t *
来打印。 - 最后可以通过
get(0).big()
获取到方法的详细信息。
验证data
数据:
- 刚开始存储的是
ro
数据,生成rw
数据后就存储rw
数据了,ro
数据存储在rw
数据中。
验证一般情况下rw
的方法列表指向ro
的方法列表:
1.2 cls关联元类与父类
//父类和元类的实例化
supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
#if SUPPORT_NONPOINTER_ISA
if (isMeta) {
//元类isa是纯指针。
cls->setInstancesRequireRawIsa();
} else {
//isa是否纯指针, flags中第13位
bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
bool rawIsaIsInherited = false;
static bool hackedDispatch = false;
//这就是环境变量中配置的 OBJC_DISABLE_NONPOINTER_ISA
if (DisableNonpointerIsa) {
// Non-pointer isa disabled by environment or app SDK version
//配置环境变量为YES后,isa是一个纯指针。
instancesRequireRawIsa = true;
}
//OS_object类时纯指针
else if (!hackedDispatch && 0 == strcmp(ro->getName(), "OS_object"))
{
// hack for libdispatch et al - isa also acts as vtable pointer
hackedDispatch = true;
instancesRequireRawIsa = true;
}
//父类是纯指针,并且父类还有父类。那么自己也要是纯指针。rawIsaIsInherited 表示继承的是纯指针
else if (supercls && supercls->getSuperclass() &&
supercls->instancesRequireRawIsa())
{
instancesRequireRawIsa = true;
rawIsaIsInherited = true;
}
//递归设置isa为纯指针,子类也设置为纯指针。(父类为纯指针,子类也为纯指针)。rawIsaIsInherited只是控制打印。
if (instancesRequireRawIsa) {
cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
}
}
// SUPPORT_NONPOINTER_ISA
#endif
//关联父类与元类。也就是继承链与isa走位。
cls->setSuperclass(supercls);
cls->initClassIsa(metacls);
- 递归实例化父类和元类。
- 判断设置
isa
是否纯指针。- 元类
isa
是纯指针。 - 类的
isa
是否纯指针取值flags
第13位。 -
DisableNonpointerIsa
也就是OBJC_DISABLE_NONPOINTER_ISA
环境变量配置来配置是否纯指针。 -
OS_object
是纯指针。 - 父类是纯指针,并且父类还有父类。那么自己也要是纯指针。
rawIsaIsInherited
(只是控制打印)表示继承的是纯指针。 - 递归设置isa为纯指针,子类也设置为纯指针。(父类为纯指针,子类也为纯指针)。
- 元类
- 关联父类与元类,也就是继承链与
isa
走位。
1.3 调整ivar offset
//调整ivar的offset,可能会重新创建`class_ro_t`来更新ivar
if (supercls && !isMeta) reconcileInstanceVariables(cls, supercls, ro);
//设置成员变量占用空间大小
cls->setInstanceSize(ro->instanceSize);
- 在有父类的情况下,并且非元类会进行
ivar offset
的调整,具体逻辑在reconcileInstanceVariables
中。 - 重新设置成员变量的大小。逻辑在
setInstanceSize
中,其中有对常量的修改。
对于成员变量的解读,将放在下一篇文章。这篇文章的重点是方法的加载。
1.4 rw同步ro标志位
//拷贝ro的flags到rw中 flags第2位 c++构造方法 RO_HAS_CXX_STRUCTORS,flags第8位 RO_HAS_CXX_DTOR_ONLY(c++析构方法)
if (ro->flags & RO_HAS_CXX_STRUCTORS) {
cls->setHasCxxDtor();
if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
cls->setHasCxxCtor();
}
}
//是否禁止关联对象 第20位标记是否允许关联对象。
if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
(supercls && supercls->forbidsAssociatedObjects()))
{
rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
}
- 拷贝
ro
的flags
到rw
中(其实是放在缓存中)。 -
RO_HAS_CXX_STRUCTORS
flags
第2
位标记c++
构造方法。 -
RO_HAS_CXX_DTOR_ONLY
flags
第8
位标记c++
析构方法。 - 自己禁止关联对象或者父类禁止关联对象(父类禁止,子类肯定禁止),则同步
RW_FORBIDS_ASSOCIATED_OBJECTS
标记。flags
第20
位标记是否禁止关联对象。
1.5 子类与根类的设置
if (supercls) {
//关联子类
addSubclass(supercls, cls);
} else {
//设置根类 nextSiblingClass 为 _firstRealizedClass 根类是第一个被实例化的类。
addRootClass(cls);
}
-
addSubclass
目标是设置父类的子类。同时也设置了子类的相邻类以及c++
构造和析构的标记。同时根据父类是否isa
纯指针同步给子类。 -
addRootClass
设置根类,在这个流程中NSObject
的相邻类会被设置为nil
,_firstRealizedClass
会被设置为NSObject
。
1.5.1 addSubclass
static void addSubclass(Class supercls, Class subcls)
{
……
if (supercls && subcls) {
……
objc_debug_realized_class_generation_count++;
//相邻类
subcls->data()->nextSiblingClass = supercls->data()->firstSubclass;
//第一个子类
supercls->data()->firstSubclass = subcls;
//同步父类的c++析构和构造
if (supercls->hasCxxCtor()) {
subcls->setHasCxxCtor();
}
if (supercls->hasCxxDtor()) {
subcls->setHasCxxDtor();
}
……
//同步子类isa是否纯指针
if (supercls->instancesRequireRawIsa() && supercls->getSuperclass()) {
subcls->setInstancesRequireRawIsaRecursively(true);
}
}
}
- 设置子类的相邻类(
nextSiblingClass
)为父类的第一个子类(firstSubclass
)。 - 设置父类的第一个子类为
cls
。 - 同步设置父类的
c++
析构和构造函数的标记给子类。 - 同步父类的
isa
是否纯指针给子类。
1.5.2 addRootClass
static void addRootClass(Class cls)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
objc_debug_realized_class_generation_count++;
//自己的相邻类设置为第一个初始化的类(nil)。第一个初始化的类设置为自己。
cls->data()->nextSiblingClass = _firstRealizedClass;
_firstRealizedClass = cls;
}
-
NSObject
的相邻类会被设置为nil
,_firstRealizedClass
会被设置为NSObject
。
但是经过断点调试发现这个方法会进入两次:
image.png
第一次符合预期。第二次是__NSAtom
进入的。它的相邻类是NSObject
,_firstRealizedClass
变成了__NSAtom
。堆栈是在+ load
方法后:
那么__NSAtom
是什么呢?根据上面的逻辑__NSAtom
的相邻类为NSObject
,第一个初始化的类变成了__NSAtom
。在objc
源码中并没有搜到相关类,只有OBJC_TAG_NSAtom
。在class-dump
的CoreFoundation
(CoreFoundation
源码中也没有开源这一部分)头文件中发下了如下声明:
@interface __NSAtom : _UKNOWN_SUPERCLASS_ {
Class isa;
}
+(void)initialize;
@end
那就证明__NSAtom
是属于CoreFoundation
的。目前暂不清楚这个类的用法以及作用。
参考
二、methodizeClass
在realizeClassWithoutSwift
中最后调用了如下代码:
// Attach categories
methodizeClass(cls, previously);
根据注释可以看到应该是对分类的处理,参数cls
没问题,previously
是从_read_images
中传过来的为nil
:
realizeClassWithoutSwift(cls, nil);
methodizeClass
核心逻辑如下:
static void methodizeClass(Class cls, Class previously)
{
runtimeLock.assertLocked();
bool isMeta = cls->isMetaClass();
auto rw = cls->data();
auto ro = rw->ro();
auto rwe = rw->ext();
……
//获取ro方法列表
method_list_t *list = ro->baseMethods();
if (list) {
//RO_FROM_BUNDLE 29 位标记是否bundleclass
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
if (rwe) rwe->methods.attachLists(&list, 1);
}
//获取ro属性列表
property_list_t *proplist = ro->baseProperties;
if (rwe && proplist) {
rwe->properties.attachLists(&proplist, 1);
}
//获取协议列表
protocol_list_t *protolist = ro->baseProtocols;
if (rwe && protolist) {
rwe->protocols.attachLists(&protolist, 1);
}
// Root classes get bonus method implementations if they don't have
// them already. These apply before category replacements.
//是否根元类,根元类加了initialize方法。
if (cls->isRootMetaclass()) {
// root metaclass
addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
}
……
分类的处理
// Attach categories.
objc::unattachedCategories.attachToClass(cls, cls,
isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
……
}
-
ro
的方法列表修正和排序。 - 方法的
attachLists
处理。(没有rwe
的情况下不走) - 属性的
attachLists
处理。(没有rwe
的情况下不走) - 协议的
attachLists
处理。(没有rwe
的情况下不走) - 根元类添加
initialize
方法。 - 分类的
attachToClass
处理。(最终不会走进attachCategories
逻辑)
2.1 prepareMethodLists
//cls, &list, 1, YES, isBundleClass(cls), nullptr
static void
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
bool baseMethods, bool methodsFromBundle, const char *why)
{
……
// Add method lists to array.
// Reallocate un-fixed method lists.
// The new methods are PREPENDED to the method list array.
//addedCount 为 1
for (int i = 0; i < addedCount; i++) {
//addedLists 也就是list。
method_list_t *mlist = addedLists[i];
ASSERT(mlist);
// Fixup selectors if necessary
//是否已经排序,没有则进行排序。对ro methodlist 排序
if (!mlist->isFixedUp()) {
//修正并且排序methodList
fixupMethodList(mlist, methodsFromBundle, true/*sort*/);
}
}
……
}
-
RO_FROM_BUNDLE
flags
第29
位标记是否bundleclass
。 -
addedCount
值为1
,addedLists
为**
类型。那么mlist
就是ro
的list
。 - 如果没有排序则修正并且排序
ro
的methodLists
。
验证mlist
:
2.1.1 fixupMethodList
static void
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
runtimeLock.assertLocked();
ASSERT(!mlist->isFixedUp());
// fixme lock less in attachMethodLists ?
// dyld3 may have already uniqued, but not sorted, the list
if (!mlist->isUniqued()) {
mutex_locker_t lock(selLock);
// Unique selectors in list.
for (auto& meth : *mlist) {
//SEL转成name字符串
const char *name = sel_cname(meth.name());
//将name和地址设置进meth中
printf("before setName:%s address:%p\n",name,meth.name());
//设置SEL,SEL有可能在 __sel_registerName 最终调用了_dyld_get_objc_selector的值,相当于修正到dyld中。
meth.setName(sel_registerNameNoLock(name, bundleCopy));
printf("after setName:%s address:%p\n",name,meth.name());
}
}
// Sort by selector address.
// Don't try to sort small lists, as they're immutable.
// Don't try to sort big lists of nonstandard size, as stable_sort
// won't copy the entries properly.
//排序,通过SEL的地址排序。samll lists 不可变,不排序。这里就与慢速消息查找的二分查找对应上了。在这里进行的排序。
if (sort && !mlist->isSmallList() && mlist->entsize() == method_t::bigSize) {
method_t::SortBySELAddress sorter;
std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter);
}
// Mark method list as uniqued and sorted.
// Can't mark small lists, since they're immutable.
//设置标志位
if (!mlist->isSmallList()) {
mlist->setFixedUp();
}
}
- 先对
method
的name
也就是SEL
进行了修正。sel_registerNameNoLock -> __sel_registerName -> search_builtins->_dyld_get_objc_selector
。相当于以dyld
的为准。 - 接着对
methodList
进行了排序(按照SEL
的地址),这里就与慢速消息查找的二分查找对应上了。排序是在这里进行的。 - 最后设置标志位。
small lists
不可变不进行排序。
修正前后SEL
验证:
发现
run
的地址发生了变化,修正为dyld
提供的地址了。
添加log
进行排序前后验证:
其它方法顺序没有发生变化,
run
发生了变化。根据以上验证可以得出以下结论:
- 方法的顺序默认是按照编译时候的顺序排序的,一般情况下是有序的。(文件中方法的顺序)
- 在进行
dyld
修正SEL
地址后需要重新排序。
2.2 分类的探索
在prepareMethodLists
执行完成后是没有rwe
数据的,所以后续的attachLists
相关操作都不会执行。根据之前WWDC
的介绍rwe
在有分类的情况下会出现,那么就加个分类:
@interface HPObject (HP)
@property (nonatomic, copy) NSString *hp_name;
@property (nonatomic, assign) int hp_age;
- (void)hp_instanceMethod1;
- (void)hp_instanceMethod2;
+ (void)hp_classMethod1;
@end
@implementation HPObject (HP)
- (void)hp_instanceMethod1 {
NSLog(@"%s",__func__);
}
- (void)hp_instanceMethod2 {
NSLog(@"%s",__func__);
}
+ (void)hp_classMethod1 {
NSLog(@"%s",__func__);
}
@end
2.2.1 clang还原底层代码
既然要研究分类的实现,不防先探索下底层的实现。
在转换后的cpp
文件中有如下实现:
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&_OBJC_$_CATEGORY_HPObject_$_HP,
};
_CATEGORY_HPObject_$_HP
是一个_category_t
类型:
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;
};
- 分类也是一个结构体类型。
-
name
的名字应该是HP
。 -
cls
指向类。 - 在类中只有一个
methods
,在分类中有了instance_methods
与class_methods
。因为分类没有元类(也就是没有分元类)。 - 分类中是有
properties
的。
接着有以下代码:
static struct _category_t _OBJC_$_CATEGORY_HPObject_$_HP __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"HPObject",//名字
0, // &OBJC_CLASS_$_HPObject,//cls
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_HPObject_$_HP,//实例方法
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_HPObject_$_HP,//类方法
0,///协议
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_HPObject_$_HP,//属性
};
- 这里
name
给了HPObject
而不是HP
,因为静态编译的时候还不知道名字。只是做了赋值。 -
cls
没有赋值,但是有注释。这个时候还没有关联,需要运行时关联。 - 协议也没有赋值。
直接遵循NSObject
协议再重新编译下:
static struct /*_protocol_list_t*/ {
long protocol_count; // Note, this is 32/64 bit
struct _protocol_t *super_protocols[1];
} _OBJC_CATEGORY_PROTOCOLS_$_HPObject_$_HP __attribute__ ((used, section ("__DATA,__objc_const"))) = {
1,
&_OBJC_PROTOCOL_NSObject
};
这个时候协议就有值了,正是NSObject
协议。
这个时候属性确实已经有了:
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[2];
} _OBJC_$_PROP_LIST_HPObject_$_HP __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
2,
{{"hp_name","T@\"NSString\",C,N"},
{"hp_age","Ti,N"}}
};
但是并没有属性队形的set & get
:
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[2];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_HPObject_$_HP __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
2,
{{(struct objc_selector *)"hp_instanceMethod1", "v16@0:8", (void *)_I_HPObject_HP_hp_instanceMethod1},
{(struct objc_selector *)"hp_instanceMethod2", "v16@0:8", (void *)_I_HPObject_HP_hp_instanceMethod2}}
};
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_HPObject_$_HP __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"hp_classMethod1", "v16@0:8", (void *)_C_HPObject_HP_hp_classMethod1}}
};
所以只能通过关联对象处理。
2.2.2 category_t 源码验证
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;
// 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;
}
};
- 在源码中
category_t
与clang
转换的c++
代码结构不同。 - 不仅类型不同,并且还有一个
_classProperties
。
2.2.3 文档探索
在Xcode
文档中搜索Category
得到以下内容:
//An opaque type that represents a category.
typedef struct objc_category *Category;
在源码中搜索objc_category
发现OBJC2_UNAVAILABLE
:
struct objc_category {
char * _Nonnull category_name OBJC2_UNAVAILABLE;
char * _Nonnull class_name OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable instance_methods OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable class_methods OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
2.2.4 分类加载源码探究
通过上面的分析,大概了解了分类的结构。分类本身是一个结构体,那么它是怎么加载的呢?
通过类的加载源码的分析核心逻辑在attachLists
与attachToClass
中。控制条件是rwe
。
rwe
来源于rw->ext()
:
auto rwe = rw->ext();
ext
实现如下:
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() {
//获取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));
}
}
在extAllocIfNeeded
中进行了rwe
的创建。extAllocIfNeeded
的调用分为以下情况(rwe
创建情况):
-
attachCategories
。 - 在
demangledName
的isRealized()
或者isFuture()
。 -
class_setVersion
类的版本设置。 -
addMethods_finish
。 -
class_addProtocol
。 -
_class_addProperty
。 -
objc_duplicateClass
。
可以看到除了attachCategories
,其它要么是对类进行动态处理要么是修复类的时候创建rwe
。这与WWDC
上的介绍就相吻合了。那么显然核心逻辑就在attachCategories
了。
而attachCategories
的调用逻辑在attachToClass
与load_categories_nolock
中。
-
attachToClass
是在methodizeClass
中调用的 -
load_categories_nolock
是在_read_images
(这里不会调用)与loadAllCategories
中。 -
loadAllCategories
是在load_images
的时候加载。
所以分类的加载就有了两条线路:
methodizeClass -> attachToClass -> attachCategories
load_images -> loadAllCategories -> load_categories_nolock -> attachCategories
分类加载的详细实现将在下篇文章分析。
三、总结
- realizeClassWithoutSwift
- 通过
ro
生成rw
数据。这里rw
只是关联到了ro
。 -
isa
判断处理,关联元类与父类。- 元类
isa
是纯指针。 -
isa
是递归设置的,父类为纯指针,子类也为纯指针。
- 元类
- 调整
ivars
的offset
- 同步
flags
的标志位给rw
(实际上是从ro
中存储到缓存中)。 - 关联子类(
firstSubclass
)与相邻的类(nextSiblingClass
)。 - methodizeClass(主要是对方法列表进行排序 & 加载分类 & rwe 的处理)
- prepareMethodLists
- fixupMethodList 修正并且排序方法列表(
ro
的)-
sel_registerNameNoLock
最终执行_dyld_get_objc_selector
将SEL
地址修复为dyld
提供的。 -
SortBySELAddress
对方法列表进行排序。
-
- fixupMethodList 修正并且排序方法列表(
-
attachToClass
分类的加载。
- prepareMethodLists
- 通过
- rwe
-
rwe
是在extAllocIfNeeded
中创建的。- 加载分类。
- 动态修改类(
addMethods_finish
、class_addProtocol
、_class_addProperty
)。
- 修复类(
demangledName
、class_setVersion
、objc_duplicateClass
)。
-
- 分类的加载有两条路径:
-
methodizeClass -> attachToClass -> attachCategories
。 -
load_images -> loadAllCategories -> load_categories_nolock -> attachCategories
。
-