前言
在类的加载原理(上)中,我们分析了_read_images
的流程,目前还有ro
和rw
的处理没有找到。但定位到了相关代码realizeClassWithoutSwift
,本文将对它进行探究
realizeClassWithoutSwift
- 查看这里面源码时,感觉有一丝熟悉感,原来在
慢速查找的条件准备阶段
走到过这里。本文使用WSPerson
类去研究realizeClassWithoutSwift
,WSPerson
类如下:
// .h
@interface WSPerson : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *nickName;
+ (void)sayNB;
+ (void)sayGood;
+ (void)sayLike;
- (void)sayABC;
- (void)sayBCD;
@end
// .m
@implementation WSPerson
+ (void)load {
NSLog(@"🎈🎈🎈%s", __func__);
}
+ (void)initialize {
NSLog(@"🎈🎈🎈%s", __func__);
}
+ (void)sayNB {
NSLog(@"%s", __func__);
}
+ (void)sayGood {
NSLog(@"%s", __func__);
}
+ (void)sayLike {
NSLog(@"%s", __func__);
}
- (void)sayABC {
NSLog(@"%s", __func__);
}
- (void)sayBCD {
NSLog(@"%s", __func__);
}
- (void)sayCDE {
NSLog(@"%s", __func__);
}
- (void)sayDEF {
NSLog(@"%s", __func__);
}
- (void)sayEFG {
NSLog(@"%s", __func__);
}
- (void)sayFGI {
NSLog(@"%s", __func__);
}
@end
- 在走到
realizeClassWithoutSwift
时,先要确定是不是要研究的WSPerson
类,可以研究模仿readClass
时的写法:
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
const char *mangledName = cls->nonlazyMangledName();
const char *person = "WSPerson";
if (strcmp(mangledName, person) == 0) {
printf("🎈🎈🎈%s --- 要研究的: %s\n", __func__, mangledName);
}
...
}
- 断点到
printf
处运行当走到断点时也就确定了当前使用WSPerson
进行研究,再Step Over
往下走
ro和rw
先走到这里:
auto ro = (const class_ro_t *)cls->data(); // clean memory 干净内存
auto isMeta = ro->flags & RO_META;
if (ro->flags & RO_FUTURE) {
// This was a future class. rw data is already allocated.
// 未来类对 ro,rw处理
rw = cls->data();
ro = cls->data()->ro();
ASSERT(!isMeta);
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// Normal class. Allocate writeable class data. // 从ro复制一份给rw
rw = objc::zalloc<class_rw_t>(); // 开辟rw空间
rw->set_ro(ro); //将ro复制一份给rw
rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
cls->setData(rw); //
}
- 先从
data()
获取ro
也就是clean Memory(干净内存)
- 如果是未来类则会走进
if
处理,而WSPerson
是已知类,根据断点走到了else
,这里面主要是对rw
处理,分为三步:-
- 开辟
rw
空间
- 开辟
-
- 将
ro
复制一份给rw
- 将
-
- 将新的
rw
放到class
的bits
中
- 将新的
-
缓存初始化
cls->cache.initializeToEmptyOrPreoptimizedInDisguise(); //缓存初始化
#if FAST_CACHE_META
if (isMeta) cls->cache.setBit(FAST_CACHE_META); // 如果是元类对缓存做一些处理
#endif
- 这里主要进行了缓存初始化,和对元类的缓存处理
父类和元类的处理
supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil); //对父类的ro,rw处理
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil); // 对元类类的ro,rw处理
#if SUPPORT_NONPOINTER_ISA
if (isMeta) {
// Metaclasses do not need any features from non pointer ISA
// This allows for a faspath for classes in objc_retain/objc_release.
// 如果是元类 isa = raw isa(原始的isa)
cls->setInstancesRequireRawIsa();
} else {
// Disable non-pointer isa for some classes and/or platforms.
// Set instancesRequireRawIsa.
bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
bool rawIsaIsInherited = false;
static bool hackedDispatch = false;
if (DisableNonpointerIsa) {
// Non-pointer isa disabled by environment or app SDK version
// 设置了环境变量OBJC_DISABLE_NONPOINTER_ISA,instancesRequireRawIsa = true
instancesRequireRawIsa = true;
}
else if (!hackedDispatch && 0 == strcmp(ro->getName(), "OS_object"))
{
// hack for libdispatch et al - isa also acts as vtable pointer
hackedDispatch = true;
instancesRequireRawIsa = true;
}
else if (supercls && supercls->getSuperclass() &&
supercls->instancesRequireRawIsa())
{
// This is also propagated by addSubclass()
// but nonpointer isa setup needs it earlier.
// Special case: instancesRequireRawIsa does not propagate
// from root class to root metaclass
instancesRequireRawIsa = true;
rawIsaIsInherited = true;
}
if (instancesRequireRawIsa) {
cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
}
}
// SUPPORT_NONPOINTER_ISA
#endif
// Update superclass and metaclass in case of remapping
cls->setSuperclass(supercls); // 设置父类
cls->initClassIsa(metacls); // 设置元类
...
// 设置父类和根类
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
- 主要是获得
cls
的父类,然后设置cls
的父类,设置cls
的isa
指向获得的元类
,然后再设置父类
和根类
DisableNonpointerIsa
就是上篇文章中提到的环境变量OBJC_DISABLE_NONPOINTER_ISA
,如果设置了环境变量或者是元类,isa
都是纯净isa(raw isa)
,如果不是元类且没有设置这个环境变量,则是Non-pointer isa
- 经过这些步骤,我们看下
ro
是否已经有值,但是我们知道元类的名字和本类一样,仅凭借名字是不能确定是本类,所以在加一个条件:
if (strcmp(mangledName, person) == 0) {
if (!isMeta) {
printf("🎉🎉🎉 %s --- 要研究的,且不是元类: %s\n", __func__, mangledName);
}
}
- 然后在
printf
处断点运行,然后打印ro
的方法: - 虽然
ro
的baseMethodList
有地址,但是并没有内容,相当于只有一个壳子,方法并没有加进来,接下来再去看看methodizeClass
方法
methodizeClass
- 因为要用
WSPerson
去研究方法列表,所以这里还是要做一下筛选,并排除元类,和上面方法一样。
分析
- 主要代码如下:
- 因为
previously
参数传过来为nil
,所以这里代码不用看,主要是对方法的赋值排序,以及对分类的属性和协议处理
prepareMethodLists
-
它的源码如下
-
主要的排序代码是
fixupMethodList
,代码如下: -
这里是遍历方法列表取出方法,然后用获取方法名字,在调用
setName
方法将名字关联,也就是imp
和sel
关联 -
调用
std::stable_sort
方法进行排序
代码验证
- 在
sort
前后分别打印方法列表: - 运行打印结果如下:
得到结论:
1.对象方法是排好序的
2.类方法中方法的顺序是变化的,需要进行排序
- 排完序后,再打印
ro
,发现还是nil
: - 依然没有获取到方法列表,由于
rwe
是nil
,跟着断点继续往下走走到了objc::unattachedCategories.attachToClass
方法
attachToClass
- 它的源码如下:
class UnattachedCategories : public ExplicitInitDenseMap<Class, category_list>
{
...
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();
auto it = map.find(previously);
if (it != map.end()) {
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);
}
map.erase(it);
}
}
}
- 这里主要将分类方法添加到元类,但是断点没走到
if (it != map.end())
,走完后rwe
也没有值。而判断里attachCategories
看似和分类有关,先来看看
attachCategories
- 再方法里找到了
rwe
的赋值
auto rwe = cls->data()->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
则返回,如果不是则创建。那么核心就是extAllocIfNeeded
什么时候被调用了。
分类的加载
全局搜索下,结果如下:
-
attachCategories
方法中调用
-
- 在
if (isRealized() || isFuture())
中调用,也就是未来类调用
- 在
-
- 在
class_setVersion
设置版本的时候调用
- 在
-
- 在方法添加完成时
addMethods_finish
调用
- 在方法添加完成时
-
- 添加协议
class_addProtocol
时调用
- 添加协议
-
- 添加属性
_class_addProperty
时调用
- 添加属性
-
- 在
objc_duplicateClass
重复的类中调用
- 在
通过这些调用,印证了
WWDC2020
中的内存,只有动态处理时系统才会对rwe
进行处理,也就是运行时会加载分类
。
- 由于签名走到最后进入了
attachCategories
方法,所以重心放到这个方法。
反向推导
- 搜索
attachCategories
在哪些地方进行调用,于是就定位到了两个方法:load_categories_nolock
和attachToClass
attachToClass
- 搜索
attachToClass
,发现只在MethodizeClass
中调用,并且受previously
参数约束,而签名分析过它传过来为nil
,所以不会走进来
load_categories_nolock
- 在研究前先定义一个
WSPerson
的分类
@interface WSPerson (App)
@property (nonatomic, copy) NSString *category_hobby;
@property (nonatomic, assign) NSInteger age;
- (void)app_categoryInstanceMethod1;
- (void)app_categoryInstanceMethod2;
+ (void)app_categoryClassMethod;
@end
@implementation WSPerson (App)
- (void)app_categoryInstanceMethod1 {
NSLog(@"%s", __func__);
}
- (void)app_categoryInstanceMethod2 {
NSLog(@"%s", __func__);
}
+ (void)app_categoryClassMethod {
NSLog(@"%s", __func__);
}
@end
- 然后在
load_categories_nolock
中增加筛选WSPerson
代码,然后运行,发现进不来。再断点在for循环处
,发现count
为0
,也就是根据没加载到类中?再来分析下load_categories_nolock
的调用
反推
- 全局搜索
load_categories_nolock
,发现有两处调用_read_images
和loadAllCategories
,再继续搜索loadAllCategories
,它知道load_images
中调用。
_read_images中调用
在read_images
中调用的注释是是在启动是出现的分类,发现被推迟到_dyld_objc_notify_register
调用完成后的第一个load_images
调用,也就是一般不会调用。
load_images中调用
断点到load_images
调用的loadAllCategories()
,然后再到loadAllCategories
的load_categories_nolock
处也加个断点,发现能走进来,但是此处依然没有加到类的分类列表,再类和分类都加上+load
方法,再尝试,发现走到了load_categories_nolock
的类筛选
分类加载流程
- 综上所述,分类的调用流程如下:
load_images
->loadAllCategories
->load_categories_nolock
->attachCategories
问题:为什么类和分类加上
+load
方法,就可以走进load_categories_nolock
方法,下一篇文章再探讨
Category
我们提到了分类,以及分类的加载流程,那么什么是分类呢,接下来去探究下
分类的本质
clang分类查看
- 用
clang
查看C++
源码:
static struct _category_t *_OBJC_LABEL_NONLAZY_CATEGORY_$[] = {
&_OBJC_$_CATEGORY_WSPerson_$_App,
};
- 在源码中,能够看到
_category_t
结构,里面能够看到WSPerson
分类的名字是App
,_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; // 属性
};
- 在
_category_t
结构体中name
是分类的名字,此处是App
cls
是类的名字instance_methods
和class_methods
分别是对象方法,和类方法,为什么会有两个方法呢,是因为分类没有元类一说
,所以会有两个变量分别记录对象方法和实例方法。protocols
是协议properties
是属性
- 继续往下看,发现
WSPerson
的_category_t
结构: _category_t
第一个参数不是App
么,怎么会是WSPerson
,第二个参数也不是WSPerson
,这是为什么?实际这是编译阶段,编译器并不知道他们是什么,所以填充个临时值,在运行时会重新进行赋值。
objc4-812中查看
- 再与
objc
源码中查看下category
objc
源码中和clang
出来的C++
代码的category
结构基本一致
所以可以得到结论
分类的本质是结构体
懒加载类和非懒加载类
疑问
- 在探索方法进入
realizeClassWithoutSwift
前,由于在WSPerson
中实现了+ load
才能走进循环:
// Realize non-lazy classes (for +load methods and static instances)
for (EACH_HEADER) {
classref_t const *classlist = hi->nlclslist(&count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
if (!cls) continue;
addClassTableEntry(cls);
...
realizeClassWithoutSwift(cls, nil);
}
}
- 这是为什么呢,注释上提示,这是个非懒加载类,只有实现了
+load
方法才能走进来realizeClassWithoutSwift
,那么去掉+load
能走进realizeClassWithoutSwift
吗?我们先注释+load
方法,然后再方法中添加WSPerson
类的识别代码:
const char *mangledName = cls->nonlazyMangledName();
const char *person = "WSPerson";
if (strcmp(mangledName, person) == 0) {
printf("🎈🎈🎈%s --- 要研究的: %s --- 是不是 元类:%d\n", __func__, mangledName, isMeta);
}
- 再打印,发现也能走进来,查看堆栈,发现它不是通过
read_images
进来的: - 此刻恍然大悟,不实现
+load
时,方法加载会走到慢速查找,也就是发送消息的时候才会去加载,这也就是懒加载类
流程总结
- 懒加载类加载流程:
lookUpImpOrForward
->realizeAndInitializeIfNeeded_locked
->realizeClassMaybeSwiftAndLeaveLocked
->realizeClassMaybeSwiftMaybeRelock
->realizeClassWithoutSwift
->methodizeClass
- 非懒加载类加载流程:
map_images
->map_images_nolock
->_read_images
->realizeClassWithoutSwift
->methodizeClass