前言
在类的加载原理(上)中,我们分析了_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是分类的名字,此处是Appcls是类的名字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