前言
上一篇文章中已经分析了_object_init
与read_images
的底层原理,最后类的加载停留在了realizeClassWithoutSwift
方法,这个也是我们这篇文章重点分析的点。
准备资料
- dyld-852
- objc4-818.2
- WWDC2020-10163
realizeClassWithoutSwift
realizeClassWithoutSwift
方法其实在类的加载中对rw
与ro
数据进行一系列的处理,在read_images
方法中进入这个方法必须实现+load
方法,使类变为非懒加载
。其核心代码实现如下:
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
runtimeLock.assertLocked();
class_rw_t *rw; //rw数据
Class supercls; //父类
Class metacls; //元类
//省略cls的初始化判断
......
//省略生成rw数据的逻辑
......
//初始化cache(缓存)
......
cls->cache.initializeToEmptyOrPreoptimizedInDisguise();
#if FAST_CACHE_META
//判断是否为元类
if (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif
//为32位架构设计的,为isa是否纯指针做的处理。
cls->chooseClassArrayIndex();
......
//递归获取元类和父类(关联元类和父类)
supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
......
//调整ivars
if (supercls && !isMeta) reconcileInstanceVariables(cls, supercls, ro);
......
//同步flags标志位
if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
(supercls && supercls->forbidsAssociatedObjects()))
{
rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
}
......
//关联子类和相邻类
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
// Attach categories
//分类处理
methodizeClass(cls, previously);
return cls;
}
realizeClassWithoutSwift
主要做了以下的操作:
- 利用
ro
数据生成rw
数据。 - 通过
isa
的判断处理,关联cls
的父类和元类,这里会递归父类
和元类
的realizeClassWithoutSwift
,最后与cls
关联。 - 调整
ivars
的offset
。 - 同步
flags
标志位给rw
。 - 关联
子类
与相邻
的类。 -
分类
的处理,在methodizeClass
方法中。(下面单独分析)
rw数据的生成
auto ro = (const class_ro_t *)cls->data();
auto isMeta = ro->flags & RO_META;
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();
//将ro数据写进rw数据中
rw->set_ro(ro);
//设置flag标志位
rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
//设置rw数据
cls->setData(rw);
}
- 通过
cls->data()->ro()
读出ro
数据,在之前的class
结构探索的时候data()
获取的是rw
数据。这是因为rw
还没有赋值,从macho
中读取__objc_classlist
就存在了data()
中了。具体可以在赋值前后验证cls->data()
的地址。 -
rw
开辟了数据空间,将新的ro
数据连接到rw
中的ro()
中。 - 设置
rw
的flags
是一个uint32_t
类型,31
位表示rw
是否已经初始化完毕(RW_REALIZED
),19
位表示rw是否初始化中(RW_REALIZING
),0
位表示是否是元类
(元类为1,非元类为0
)。 - 将新的
rw
数据放在data()
中。
注意:ro
数据是在llvm
编译期间已经生成。
cls关联元类与父类
//递归获取元类和父类
supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
#if SUPPORT_NONPOINTER_ISA
if (isMeta) {
//元类的isa是纯isa
cls->setInstancesRequireRawIsa();
} else {
bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
bool rawIsaIsInherited = false;
static bool hackedDispatch = false;
if (DisableNonpointerIsa) {
// Non-pointer isa disabled by environment or app SDK version
//设置环境变量,此时isa为纯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、指针
if (instancesRequireRawIsa) {
cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
}
}
// SUPPORT_NONPOINTER_ISA
#endif
// Update superclass and metaclass in case of remapping
//关联父类和元类,也是继承链与isa走位
cls->setSuperclass(supercls);
cls->initClassIsa(metacls);
-
递归
实例化元类
和父类
。 - 判断设置
isa
是否为纯isa
。- 元类的
isa
是纯isa
。 - 类的
isa
是否为纯isa
取决于flag
的第13
标志位。 -
OS_object
是纯指针。 - 递归设置
isa
为纯指针,子类
也设置为纯指针
。(父类为纯指针,子类也为纯指针
)。
- 元类的
- 关联
父类
和元类
也是根据isa
走位和继承链
。
调整ivar偏移(offset)
//调整ivar 的offset 可能会重新创建`class_ro_t`来更新ivar
if (supercls && !isMeta) reconcileInstanceVariables(cls, supercls, ro);
//设置ivar成员占用空间大小
cls->setInstanceSize(ro->instanceSize);
- 在有
父类
的情况下,并且非元类
会进行ivar offset
的调整,具体逻辑在reconcileInstanceVariables
中。 - 重新设置成员变量的
大小
。逻辑在setInstanceSize
中,其中有对常量
的修改。(这一点会在下篇文章重点分析)
同步flags标志位
//拷贝ro的flags到rw中
if (ro->flags & RO_HAS_CXX_STRUCTORS) {
cls->setHasCxxDtor();
if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
cls->setHasCxxCtor();
}
}
//是否禁止关联对象
if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
(supercls && supercls->forbidsAssociatedObjects()))
{
rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
}
- 拷贝
ro
的flag
放到rw
中。(其实将ro
的flag
放到缓存
) - 判断是否禁止
关联对
象,修改对应的flag
标志位。
子类和根类的设置
if (supercls) {
// 关联子类
addSubclass(supercls, cls);
} else {
//设置根类
addRootClass(cls);
}
-
addSubclass
目标是设置父类的子类
。同时也设置了子类的相邻类
以及c++
构造和析构的标记。同时根据父类
是否isa
纯指针同步给子类。 -
addRootClass
设置根类
,在这个流程中NSObject
的相邻类
会被设置为nil
,_firstRealizedClass
会被设置为NSObject
。
addSubclass
static void addSubclass(Class supercls, Class subcls)
{
runtimeLock.assertLocked();
if (supercls && subcls) {
ASSERT(supercls->isRealized());
ASSERT(subcls->isRealized());
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();
}
objc::AWZScanner::scanAddedSubClass(subcls, supercls);
objc::RRScanner::scanAddedSubClass(subcls, supercls);
objc::CoreScanner::scanAddedSubClass(subcls, supercls);
if (!supercls->allowsPreoptCaches()) {
subcls->setDisallowPreoptCachesRecursively(__func__);
} else if (!supercls->allowsPreoptInlinedSels()) {
subcls->setDisallowPreoptInlinedSelsRecursively(__func__);
}
//同步子类isa是否纯指针
if (supercls->instancesRequireRawIsa() && supercls->getSuperclass()) {
subcls->setInstancesRequireRawIsaRecursively(true);
}
}
}
- 设置子类的
相邻类
(nextSiblingClass
)为父类的第一个
子类(firstSubclass
)。 - 设置父类的第一个子类为
cls
。 - 同步父类的
C++
析构和构造函数的标记给子类。 - 同步父类的
isa
指针是否为纯指针给子类。
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
。
methodizeClass
上面分析的realizeClassWithoutSwift
方法最后实现了methodizeClass
方法,主要是添加分类的操作。代码如下:
//添加分类
methodizeClass(cls, previously);
- 参数
cls
就是当前类,previously
传进来是nil。
在_read_image方法中调用:
realizeClassWithoutSwift(cls, nil);
methodizeClass
核心代码如下:
static void methodizeClass(Class cls, Class previously)
{
runtimeLock.assertLocked();
bool isMeta = cls->isMetaClass();
auto rw = cls->data(); //获取rw数据
auto ro = rw->ro(); //获取rw中的ro数据
auto rwe = rw->ext(); //获取rw中的rwe数据(可扩展)
// Methodizing for the first time
//获取ro方法列表
if (PrintConnecting) {
_objc_inform("CLASS: methodizing class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
// Install methods and properties that the class implements itself.
//ro方法列表获取
method_list_t *list = ro->baseMethods();
if (list) {
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.
//分类的处理
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);
......(省略部分代码)
-
ro
的方法列表修正和排序。- 方法的
attachLists
处理。(没有rwe
的情况下不走) - 属性的
attachLists
处理。(没有rwe
的情况下不走) - 协议的
attachLists
处理。(没有rwe
的情况下不走)
- 方法的
- 在根元类中添加initialize方法。
- 分类的
attachToClass
处理。(最终不会
走进attachCategories
逻辑)
注意:attachCategories
方法在attachToClass
方法里面调用。
prepareMethodLists
方法入口:
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
此方法是获取ro的方法列表,核心代码如下:
//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*/);
}
}
……省略一部分代码
}
-
addedCount
值为1
,addedLists
为**
(指针的地址)类型。那么mlist
就是ro
的list
。 - 如果
没有
排序则修正并且排序ro
的methodLists
。
mlist
验证:
结论:
list
跟mlist
是同一个地址,也就是他们是一样
的。(通过地址偏移
的方式找出list
然后赋值给mlist
)
fixupMethodList
static void
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
//runtime的锁操作
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中
///设置SEL,SEL有可能在 __sel_registerName 最终调用了_dyld_get_objc_selector的值,相当于修正到dyld中
// printf("前面 -- name:%s -- address:%p\n",name,meth.name());
meth.setName(sel_registerNameNoLock(name, bundleCopy));
// printf("后面 -- name:%s -- address:%p\n",name,meth.name());
}
}
//排序,通过SEL的地址排序。
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 list
是不可变的,也不会进行排序。
修正前后的SEL
验证:
结论:
run
和eat
的地址发生了变化,变成了dyld
提供的地址了。
排序前后的验证:
-
排序前:
-
排序后:
明显就是自定义
的方法发生了顺序的改变,系统级别
的方法是没有改变的。 方法的顺序默认是按照编译时候的顺序排序的,一般情况下是有序的。
在进行
dyld
修正SEL
地址后需要重新排序
。
分类的探索
根据以上的代码分析可知在prepareMethodLists
执行完成后是没有rwe
数据的,所以后续的attachLists
相关操作都不会执行,那么我们添加一个分类如下:
#import "XJLPerson.h"
NS_ASSUME_NONNULL_BEGIN
@interface XJLPerson (test)
@property (nonatomic,strong) NSString *name_test;
@property (nonatomic,assign) NSInteger age_test;
-(void)test;
-(void)sayNB;
#import "XJLPerson+test.h"
@implementation XJLPerson (test)
-(void)test{
NSLog(@"---xjl---%s",__func__);
}
-(void)sayNB{
NSLog(@"---xjl---%s",__func__);
}
@end
@end
NS_ASSUME_NONNULL_END
编译项目使用Xrun
导出底层代码(main.cpp
)查看分类的实现:
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&_OBJC_$_CATEGORY_XJLPerson_$_test,
};
可以看出_XJLPerson_$_test
是_category_t
类型的。
查看_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
的名字估计是test
。 -
cls
指向类
。 - 在类中只有一个
methods
,在分类中有了instance_methods
与class_methods
。因为分类没有元类
(也就是没有分元类
)。 - 分类中是有
properties
的。
继续观察分类底层生成信息:
static struct _category_t _OBJC_$_CATEGORY_XJLPerson_$_test __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"XJLPerson",//名字
0, // &OBJC_CLASS_$_XJLPerson,//cls
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_XJLPerson_$_test,//实例方法
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_XJLPerson_$_test,//类方法
0,///协议
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_XJLPerson_$_test,//属性
};
- name赋值了
XJLPerson
而不是test
,因为静态编译
的时候还不知道名字,所以拿类的名称来赋值。 -
cls
没有赋值,但是有注释。这个时候还没有关联
,需要运行时关联
。 -
协议
也没有赋值。
查看分类的属性情况:
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_XJLPeron_$_test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
2,
{{"name_test","T@\"NSString\",C,N"},
{"age_test","Ti,N"}}
};
此时属性已经有了,但是通过之后通过方法生成的地方查找不到属性的setter
与getter
方法,这只能通过关联对象
进行处理哦。
分类加载源码的探究
通过上面的分析,大概了解了分类的结构。分类本身是一个结构体
,那么它是怎么加载
的呢?通过类的加载源码的分析核心逻辑在attachLists
与attachToClass
中。控制条件是rwe
。
auto rwe = rw->ext();
进入ext()
方法查看核心代码:
class_rw_ext_t *ext() const {
return get_ro_or_rwe().dyn_cast(&ro_or_rw_ext);
}
class_rw_ext_t *extAllocIfNeeded() {
//获取rwe
auto v = get_ro_or_rwe();
if (fastpath(v.is())) {
return v.get(&ro_or_rw_ext);
} else {
//创建rwe
return extAlloc(v.get(&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
的时候加载。
那么我们就能得出分类的加载只有以下的两条线路:
1. methodizeClass
-> attachToClass
-> attachCategories
2. load_images
-> loadAllCategories
-> load_categories_nolock
-> attachCategories
注意:分类的加载在下一篇文章会进行详细的分析哦。
总结
realizeClassWithoutSwift
- 通过
ro
生成rw
数据。这里rw
只是关联到了ro
。 -
isa
判断处理,关联元类
与父类
。- 元类的isa指针是纯isa指针。
- 类的
isa
指针是通过递归
进行设置的,父类的isa
指针为纯isa
指针的话,它的子类的isa
指针也为纯isa指针
。
- 调整
ivars
的offset
。 - 同步
flags
的标志位给rw
。(其实是将ro
数据读到了缓存
中) - 关联子类(
firstSubclass
)与相邻的类(nextSiblingClass
)。
methodizeClass
主要是对方法列表进行排序
&加载分类
& rwe
的处理。
-
prepareMethodLists
-fixupMethodList
修正并且排序方法列表(ro
的)-
sel_registerNameNoLock
最终执行_dyld_get_objc_selector
将SEL
地址修复为dyld
提供的。 -
SortBySELAddress
对方法列表进行排序。
-
-
attachToClass
分类的加载。
rwe()方法
-
rwe
是在extAllocIfNeeded
中创建的。- 加载分类。
- 动态修改类(
addMethods_finish
、class_addProtocol
、_class_addProperty
)。
- 修复类(
demangledName
、class_setVersion
、objc_duplicateClass
)。
分类加载的两条线路:
1. methodizeClass
-> attachToClass
-> attachCategories
2. load_images
-> loadAllCategories
-> load_categories_nolock
-> attachCategories