这是我参与8月更文挑战的第4天,活动详情查看: 8月更文挑战
readClass(类有了名字和地址)
在read_image的流程中我们知道经过readClass之后,类有了名字和地址;那么其流程究竟是如何处理的呢?源码如下:
/***********************************************************************
* readClass
* Read a class and metaclass as written by a compiler.
* Returns the new class pointer. This could be:
* - cls
* - nil (cls has a missing weak-linked superclass)
* - something else (space for this class was reserved by a future class)
*
* Note that all work performed by this function is preflighted by
* mustReadClasses(). Do not change this function without updating that one.
*
* Locking: runtimeLock acquired by map_images or objc_readClassPair
**********************************************************************/
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
// 获取到类的名字
const char *mangledName = cls->nonlazyMangledName();
// 当前类的父类中如有丢失的weak-linked类,则返回nil,非研究核心
if (missingWeakSuperclass(cls)) {
// No superclass (probably weak-linked).
// Disavow any knowledge of this subclass.
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING class '%s' with "
"missing weak-linked superclass",
cls->nameForLogging());
}
addRemappedClass(cls, nil);
cls->setSuperclass(nil);
return nil;
}
cls->fixupBackwardDeployingStableSwift();
// 判断是不是需要处理的未来类,正常情况下不会执行到这个条件里
// 通过断点调试,确实没有进入if判断,因此此处不会对 ro 和 rw进行操作
Class replacing = nil;
if (mangledName != nullptr) {
if (Class newCls = popFutureNamedClass(mangledName)) {
// This name was previously allocated as a future class.
// Copy objc_class to future class's struct.
// Preserve future's rw data block.
if (newCls->isAnySwift()) {
_objc_fatal("Can't complete future class request for '%s' "
"because the real class is too big.",
cls->nameForLogging());
}
class_rw_t *rw = newCls->data();
const class_ro_t *old_ro = rw->ro();
memcpy(newCls, cls, sizeof(objc_class));
// Manually set address-discriminated ptrauthed fields
// so that newCls gets the correct signatures.
newCls->setSuperclass(cls->getSuperclass());
newCls->initIsa(cls->getIsa());
rw->set_ro((class_ro_t *)newCls->data());
newCls->setData(rw);
freeIfMutable((char *)old_ro->getName());
free((void *)old_ro);
addRemappedClass(cls, newCls);
replacing = cls;
cls = newCls;
}
}
// 判断当前的类是否存在
if (headerIsPreoptimized && !replacing) {
// class list built in shared cache
// fixme strict assert doesn't work because of duplicates
// ASSERT(cls == getClass(name));
ASSERT(mangledName == nullptr || getClassExceptSomeSwift(mangledName));
} else {
if (mangledName) { //some Swift generic classes can lazily generate their names
addNamedClass(cls, mangledName, replacing); // 如果存在,将类加载到哈希表gdb_objc_realized_classes中
} else {
Class meta = cls->ISA();
const class_ro_t *metaRO = meta->bits.safe_ro();
ASSERT(metaRO->getNonMetaclass() && "Metaclass with lazy name must have a pointer to the corresponding nonmetaclass.");
ASSERT(metaRO->getNonMetaclass() == cls && "Metaclass nonmetaclass pointer must equal the original class.");
}
// 插入表,即相当于从mach-O文件 读取到 内存 中
addClassTableEntry(cls);
}
// for future reference: shared cache never contains MH_BUNDLEs
if (headerIsBundle) {
cls->data()->flags |= RO_FROM_BUNDLE;
cls->ISA()->data()->flags |= RO_FROM_BUNDLE;
}
return cls;
}
cls->nonlazyMangledName():获取到类的名字;missingWeakSuperclass(cls):判断当前类的父类中是否有weak-linked类;addNamedClass:将类加载到哈希表gdb_objc_realized_classes中;addClassTableEntry:将类插入到表中,相当于把类从MachO文件读取到内存中;
为了便于调试,我们在此处做拦截,拦截我们自定义的类Person,然后逐步研究:
继续调试:
addNamedClass方法源码如下:
将类添加到之前创建的
gdb_objc_realized_classes哈希表中,此哈希表用来存放所有的类
继续执行代码:
addClassTableEntry方法源码如下:
通过此方法,将类添加到
allocatedClasses表中,这个表在_objc_init函数中,执行runtime_init时创建;
继续执行,程序直接return,说明此处并未对ro和rw进行操作;
继续执行断点:
方法realizeClassWithoutSwift被调用;这个方法是我们接下来研究的重点,那么它做了哪些事情呢?
realizeClassWithoutSwift(处理ro,rw,isa,父类,元类)
person需要实现load方法
此方法在之前的
消息的慢速查找中曾经提到过,慢速查找的流程lookUpImpOrForward-->realizeClassMaybeSwiftAndLeavelLocked-->realizeClassMayBeSwiftMaybeRelock-->realizeClassWithoutSwift
根据注释,此处最终返回了一个类的真实的数据结构,那么它里边都做了那些处理呢?继续断点执行:
该方法主要是实现类,然后将类的数据加载到内存中,步骤如下:
- 读取
data数据,设置ro和rw - 递归调用
realizeClassWithoutSwift完善继承链 - 通过
methodizeClass方法初始化类
读取data数据
在这里,我们打印了一下ro,发现ro中已经有了一些数据,那么都有什么数据呢?因为ro是一个class_ro_t类型的数据,接下来我们就可以通过一下class_ro_t的一些方法来打印数据:
结果,在ro中已经存了在8个方法,那么都是什么方法呢:
(lldb) p $5.get(0).big()
(method_t::big) $8 = {
name = "name"
types = 0x0000000100003f98 "@16@0:8"
imp = 0x0000000100003cb0 (KCObjcBuild`-[Person name])
}
(lldb) p $5.get(1).big()
(method_t::big) $9 = {
name = "setName:"
types = 0x0000000100003fa0 "v24@0:8@16"
imp = 0x0000000100003ce0 (KCObjcBuild`-[Person setName:])
}
(lldb) p $5.get(2).big()
(method_t::big) $10 = {
name = "nickName"
types = 0x0000000100003f98 "@16@0:8"
imp = 0x0000000100003d10 (KCObjcBuild`-[Person nickName])
}
(lldb) p $5.get(3).big()
(method_t::big) $11 = {
name = "setNickName:"
types = 0x0000000100003fa0 "v24@0:8@16"
imp = 0x0000000100003d40 (KCObjcBuild`-[Person setNickName:])
}
(lldb) p $5.get(4).big()
(method_t::big) $12 = {
name = "job"
types = 0x0000000100003f98 "@16@0:8"
imp = 0x0000000100003d70 (KCObjcBuild`-[Person job])
}
(lldb) p $5.get(5).big()
(method_t::big) $13 = {
name = "setJob:"
types = 0x0000000100003fa0 "v24@0:8@16"
imp = 0x0000000100003da0 (KCObjcBuild`-[Person setJob:])
}
(lldb) p $5.get(6).big()
(method_t::big) $14 = {
name = "job1"
types = 0x0000000100003f98 "@16@0:8"
imp = 0x0000000100003dd0 (KCObjcBuild`-[Person job1])
}
(lldb) p $5.get(7).big()
(method_t::big) $15 = {
name = "setJob1:"
types = 0x0000000100003fa0 "v24@0:8@16"
imp = 0x0000000100003e00 (KCObjcBuild`-[Person setJob1:])
}
(lldb)
Person类的属性name,nickName,job,job1四个属性的setter和getter方法都已经加载了
断点继续向下执行:
在此处,把类的干净内存复制给一份,开始给rw赋值**(WWDC2020中针对运行时的优化中讲到,rw来自于ro,ro是只读的,而rw是可读可写的)**,至此,类具备了读写功能;
ro表示readOnly,即只读;在编译期就确定了内存,包含Flags,Size,Name,Methods,Protocols,Ivars和Properties;属于clean memory,是加载后就不会发生更改的内存rw表示readWrite,即可读可写;由于其动态性,可能会在类中添加属性,方法,协议;属于dirty memory,是指在运行时会发生更改的内存,类一经使用就会变成dirty memory;rwe是类的额外信息;WWDC2020提到过,大概只有10%的类会真正的修改它们的方法,所以就提出了rwe的概念;对于那些确实需要额外信息的类,才分配rwe,并将其添加到类中使用
递归调用realizeClassWithoutSwift
在该方法中,通过递归调用当前方法,来完善继承链,并设置当前类,父类和元类的数据
继续向下执行:
设置父类和元类的isa;
给当前类的父类和元类赋值;
然后通过addSubclass完善当前类信息,父类和子类关系
// Connect this class to its superclass's subclass lists
// 双向链表指向关系 父类中可以找到子类 子类中也可以找到父类
// 通过addSubclass把当前类放到父类的子类列表中去
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
接下来将会执行到:
在方法methodizeClass中对类进行类别的附加;
methodizeClass
根据方法注释可以了解到在此方法中,将属性列表,方法列表,协议列表以及分类中的方法附加给了类的rwe;
list中包含了4个属性的setter和getter方法,还有我们自定义的sayHello方法
prepareMethodLists(写入方法名并进行排序)
断点进入方法:
断点执行:
进入方法fixupMethodList修复方法列表;
将方法名字name赋值给meth;
在此处针对方法进行排序;
我们来验证一下这个排序的情况,给Person添加方法:
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, copy) NSString *job;
@property (nonatomic, copy) NSString *job1;
+ (void)sayHello;
- (void)run;
- (void)smile;
- (void)eat;
- (void)talk;
- (void)learn;
@end
然后,修改main方法中的调用:
int main(int argc, const char * argv[]) {
@autoreleasepool {
[Person sayHello];
Person *p = [Person alloc];
p.name = @"姓名";
[p learn];
NSLog(@"-->%@", p);
}
return 0;
}
然后在此处打印排序前后方法的顺序:
查看打印结果:
结论:方法前后确实被排序了
出现0x7fff********这样的地址,目前经过测试发现是因为有属性的原因,如果在main方法中只创建了类对象,而没有调用方法,那么方法排序前就会是顺序的
继续向下执行:
开始处理rwe,但是经过断点发现,程序并未执行进入attachLists方法,也就是此处rwe并没有值,那么rwe什么时候才会赋值呢?在此之前,需要补充一些关于懒加载类和非懒加载类的知识;
懒加载类和非懒加载类
在调用realizeClassWithoutSwift方法的时候,根据注释可以知道,此处实现的是非懒加载类,那么究竟什么是非懒加载类?什么是非懒加载类呢?
根据注释_实现load方法或者静态实例_,那么是不是没有load方法,realizeClassWithoutSwift就不会执行呢?我们尝试将Person类的load方法注释,验证一下:
运行源码程序:
发现没有类load方法,此处将不再执行;
实现
load方法,将一个类变成了非懒加载类
那么,既然realizeClassWithoutSwift里边处理类的数据结构,那么如果没有实现load方法的懒加载类在什么时候处理呢?
我们来通过堆栈信息确认一下:
非懒加载类的堆栈信息:
非懒加载类通过_read_images流程调用realizeClassWithoutSwift
懒加载类的堆栈信息:
懒加载类通过lookUpImpOrForward流程调用realizeClassWithoutSwift