0%

iOS底层探索 - 类的结构(下)

上一篇文章中我们探索了类的结构,留下了一个疑问就是class_ro_tclass_rw_t的区别,我们从这个区别开始。

class_ro_tclass_rw_t的区别

class_ro_t

class_ro_t存储了当前类在编译期就已经确定的属性方法和遵循的协议,里面没有category的方法。运行时添加的方法存储在运行时生成的class_rw_t中。

ro标示的是read only,是无法进行修改的,我们看一下它的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
union {
const uint8_t * ivarLayout;
Class nonMetaclass;
};
explicit_atomic<const char *> name;
// With ptrauth, this is signed if it points to a small list, but
// may be unsigned if it points to a big list.
void *baseMethodList;//方法列表的获取方法
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;//成员变量列表
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;//属性列表

// This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
_objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];
_objc_swiftMetadataInitializer swiftMetadataInitializer() const {
if (flags & RO_HAS_SWIFT_INITIALIZER) {
return _swiftMetadataInitializer_NEVER_USE[0];
} else {
return nil;
}
}
const char *getName() const {
return name.load(std::memory_order_acquire);
}
static const uint16_t methodListPointerDiscriminator = 0xC310;
#if 0 // FIXME: enable this when we get a non-empty definition of __ptrauth_objc_method_list_pointer from ptrauth.h.
static_assert(std::is_same<
void * __ptrauth_objc_method_list_pointer *,
void * __ptrauth(ptrauth_key_method_list_pointer, 1, methodListPointerDiscriminator) *>::value,
"Method list pointer signing discriminator must match ptrauth.h");
#endif
method_list_t *baseMethods() const {
#if __has_feature(ptrauth_calls)
method_list_t *ptr = ptrauth_strip((method_list_t *)baseMethodList, ptrauth_key_method_list_pointer);
if (ptr == nullptr)
return nullptr;
// Don't auth if the class_ro and the method list are both in the shared cache.
// This is secure since they'll be read-only, and this allows the shared cache
// to cut down on the number of signed pointers it has.
bool roInSharedCache = objc::inSharedCache((uintptr_t)this);
bool listInSharedCache = objc::inSharedCache((uintptr_t)ptr);
if (roInSharedCache && listInSharedCache)
return ptr;

// Auth all other small lists.
if (ptr->isSmallList())
ptr = ptrauth_auth_data((method_list_t *)baseMethodList,
ptrauth_key_method_list_pointer,
ptrauth_blend_discriminator(&baseMethodList,
methodListPointerDiscriminator));
return ptr;
#else
return (method_list_t *)baseMethodList;
#endif
}
///省略代码
};

class_rw_t

类中的属性、方法还有遵循的协议等信息都保存在 class_rw_t中,它是可读可写的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint16_t witness;
#if SUPPORT_INDEXED_ISA
uint16_t index;
#endif
explicit_atomic<uintptr_t> ro_or_rw_ext;
Class firstSubclass;
Class nextSiblingClass;
///省略代码
const method_array_t methods() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
} else {
return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods()};
}
}
const property_array_t properties() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
} else {
return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
}
}

const protocol_array_t protocols() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;
} else {
return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
}
}
};

class_rw_t生成在运行时,在编译期间,class_ro_t结构体就已经确定,objc_class中的bitsdata部分存放着该结构体的地址。在runtime运行之后,具体说来是在运行runtimerealizeClass 方法时,会生成class_rw_t结构体,该结构体包含了class_ro_t,并且更新data部分,换成class_rw_t结构体的地址。

WWDC2020中类结构优化

二进制类在磁盘中的表现是:

截屏2021-06-21 上午9.49.58

类对象本身,包含最常访问的信息:指向元类,超类和方法缓存的指针,在类结构之中有指向包含更多数据的结构体class_ro_t的指针,包含了类的名称,方法,协议,实例变量等等编译期确定的信息。其中 ro 表示 read only 的意思。

当类被Runtime加载之后,类的结构会发生一些变化在了解这些变化之前,我们需要知道2个概念:

Clean Memory:加载后不会发生更改的内存块,class_ro_t属于Clean Memory,因为它是只读的。
Dirty Memory:运行时会进行更改的内存块,类一旦被加载,就会变成Dirty Memory,例如,我们可以在 Runtime 给类动态的添加方法。

Dirty MemoryClean Memory要昂贵的多,因为它需要更多的内存信息,并且只要进程正在运行,就必须保留它。对于我们来说,越多的Clean Memory显然是更好的,因为它可以节约更多的内存。我们可以通过分离出永不更改的数据部分,将大多数类数据保留为Clean Memory,应该怎么做呢?

我们先看一下,类加载后的结构

截屏2021-06-21 上午9.50.15

在类加载到 Runtime 中后会被分配用于读取/写入数据的结构体class_rw_t

事实证明,class_rw_t会占用比class_ro_t占用更多的内存,在 iPhone 中,我们在系统测量了大约 30MB 的这些class_rw_t结构。应该如何优化这些内存呢?通过测量实际设备上的使用情况,我们发现大约 10% 的类实际会存在动态的更改行为,如动态添加方法,使用 Category 方法等。因此,我们能可以把这部分动态的部分提取出来,我们称之为class_rw_ext_t,所以,结构会变成这个样子。

截屏2021-06-21 上午9.50.41

经过拆分,可以把 90% 的类优化为Clean Memory,在系统层面,取得效果是节省了大约 14MB 的内存,使内存可用于更有效的用途。

更多内容可以查看苹果方法视频

小结

class_ro_t存放的是编译期间就确定的;而class_rw_t是在runtime时才确定,它会先将class_ro_t的内容拷贝过去,然后再将当前类的分类的这些属性、方法等拷贝到其中。所以可以说class_rw_t是class_ro_t的超集,当然实际访问类的方法、属性等也都是访问的class_rw_t中的内容。

Type Encodings

在前面我们执行clangmain.m文件转换成main.cpp文件的时候,我们发现有一些编码,例如

{(struct objc_selector *)"setNickName:", "v24@0:8@16", (void *)_I_JSPerson_setNickName_},中的v24@0:8@16,这其实就是运行时中的encode编码,它的编码表如下:

截屏2021-06-20 20.05.54

苹果官方链接地址

v24@0:8@16的含义也就是:

  • v:void
  • 24:占用的内存
  • @: 对象类型参数self
  • 0:上面参数从0位置开始
  • :: SEL
  • 8:SEL8位置开始
  • @:对象类型,实际传入的第一个参数
  • 16:从16位置开始

setter方法底层

我们首先定义一个JSPerson类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@interface JSPerson : NSObject
{
NSString *hobby; //
int a;
NSObject *objc; //
}

@property (nonatomic, copy) NSString *nickName;
@property (atomic, copy) NSString *acnickName;
@property (nonatomic) NSString *nnickName;
@property (atomic) NSString *anickName;

@property (nonatomic, strong) NSString *name;
@property (atomic, strong) NSString *aname;

@end

@implementation JSPerson


@end

通过clang命令将其转换成转换成c++代码:

clang -rewrite-objc main.m -o main.cpp

我们在main.cpp全局搜索JSPerson,定位到类的方法的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// @implementation JSPerson
static NSString * _I_JSPerson_nickName(JSPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_JSPerson$_nickName)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_JSPerson_setNickName_(JSPerson * self, SEL _cmd, NSString *nickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct JSPerson, _nickName), (id)nickName, 0, 1); }

extern "C" __declspec(dllimport) id objc_getProperty(id, SEL, long, bool);

static NSString * _I_JSPerson_acnickName(JSPerson * self, SEL _cmd) { typedef NSString * _TYPE;
return (_TYPE)objc_getProperty(self, _cmd, __OFFSETOFIVAR__(struct JSPerson, _acnickName), 1); }
static void _I_JSPerson_setAcnickName_(JSPerson * self, SEL _cmd, NSString *acnickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct JSPerson, _acnickName), (id)acnickName, 1, 1); }

static NSString * _I_JSPerson_nnickName(JSPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_JSPerson$_nnickName)); }
static void _I_JSPerson_setNnickName_(JSPerson * self, SEL _cmd, NSString *nnickName) { (*(NSString **)((char *)self + OBJC_IVAR_$_JSPerson$_nnickName)) = nnickName; }

static NSString * _I_JSPerson_anickName(JSPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_JSPerson$_anickName)); }
static void _I_JSPerson_setAnickName_(JSPerson * self, SEL _cmd, NSString *anickName) { (*(NSString **)((char *)self + OBJC_IVAR_$_JSPerson$_anickName)) = anickName; }

static NSString * _I_JSPerson_name(JSPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_JSPerson$_name)); }
static void _I_JSPerson_setName_(JSPerson * self, SEL _cmd, NSString *name) { (*(NSString **)((char *)self + OBJC_IVAR_$_JSPerson$_name)) = name; }

static NSString * _I_JSPerson_aname(JSPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_JSPerson$_aname)); }
static void _I_JSPerson_setAname_(JSPerson * self, SEL _cmd, NSString *aname) { (*(NSString **)((char *)self + OBJC_IVAR_$_JSPerson$_aname)) = aname; }
// @end

可以看到,各个属性的getset方法如上,可以发现,不同属性的set方法执行的方法不一定相同,比如:

  • nameset方法:static void _I_JSPerson_setName_(JSPerson * self, SEL _cmd, NSString *name) { (*(NSString **)((char *)self + OBJC_IVAR_$_JSPerson$_name)) = name; }
  • nickNameset方法:static void _I_JSPerson_setNickName_(JSPerson * self, SEL _cmd, NSString *nickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct JSPerson, _nickName), (id)nickName, 0, 1); }

nameset方法内存平移nickNameset方法调用的是objc_setProperty,这两种方式的是怎么确定的呢,什么情况下属性的set方法调用objc_setProperty方法呢?探究这个问题我们就需要使用LLVM了,我们从github上下载LLVM的源码,下载地址

LLVM源码里我们全局搜索objc_setProperty,查找调用这个方法的地方,发现了getSetPropertyFn方法,它的返回值是CGM.CreateRuntimeFunction(FTy, "objc_setProperty");

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
llvm::FunctionCallee getSetPropertyFn() {
CodeGen::CodeGenTypes &Types = CGM.getTypes();
ASTContext &Ctx = CGM.getContext();
// void objc_setProperty (id, SEL, ptrdiff_t, id, bool, bool)
CanQualType IdType = Ctx.getCanonicalParamType(Ctx.getObjCIdType());
CanQualType SelType = Ctx.getCanonicalParamType(Ctx.getObjCSelType());
CanQualType Params[] = {
IdType,
SelType,
Ctx.getPointerDiffType()->getCanonicalTypeUnqualified(),
IdType,
Ctx.BoolTy,
Ctx.BoolTy};
llvm::FunctionType *FTy =
Types.GetFunctionType(
Types.arrangeBuiltinFunctionDeclaration(Ctx.VoidTy, Params));
return CGM.CreateRuntimeFunction(FTy, "objc_setProperty");
}

接下来搜索关键字getSetPropertyFn(),同样是查找调用的地方:

1
2
3
llvm::FunctionCallee GetPropertySetFunction() override {
return ObjCTypes.getSetPropertyFn();
}

这里只是一个中间调用的方法,我们继续搜索关键字GetPropertySetFunction(),找调用的地方:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
void
CodeGenFunction::generateObjCSetterBody(const ObjCImplementationDecl *classImpl,
const ObjCPropertyImplDecl *propImpl,
llvm::Constant *AtomicHelperFn) {
//省略代码
switch (strategy.getKind()) {
case PropertyImplStrategy::Native: {
// We don't need to do anything for a zero-size struct.
if (strategy.getIvarSize().isZero())
return;

Address argAddr = GetAddrOfLocalVar(*setterMethod->param_begin());

LValue ivarLValue =
EmitLValueForIvar(TypeOfSelfObject(), LoadObjCSelf(), ivar, /*quals*/ 0);
Address ivarAddr = ivarLValue.getAddress(*this);

// Currently, all atomic accesses have to be through integer
// types, so there's no point in trying to pick a prettier type.
llvm::Type *bitcastType =
llvm::Type::getIntNTy(getLLVMContext(),
getContext().toBits(strategy.getIvarSize()));

// Cast both arguments to the chosen operation type.
argAddr = Builder.CreateElementBitCast(argAddr, bitcastType);
ivarAddr = Builder.CreateElementBitCast(ivarAddr, bitcastType);

// This bitcast load is likely to cause some nasty IR.
llvm::Value *load = Builder.CreateLoad(argAddr);

// Perform an atomic store. There are no memory ordering requirements.
llvm::StoreInst *store = Builder.CreateStore(load, ivarAddr);
store->setAtomic(llvm::AtomicOrdering::Unordered);
return;
}

case PropertyImplStrategy::GetSetProperty:
case PropertyImplStrategy::SetPropertyAndExpressionGet: {

llvm::FunctionCallee setOptimizedPropertyFn = nullptr;
llvm::FunctionCallee setPropertyFn = nullptr;
if (UseOptimizedSetter(CGM)) {
// 10.8 and iOS 6.0 code and GC is off
setOptimizedPropertyFn =
CGM.getObjCRuntime().GetOptimizedPropertySetFunction(
strategy.isAtomic(), strategy.isCopy());
if (!setOptimizedPropertyFn) {
CGM.ErrorUnsupported(propImpl, "Obj-C optimized setter - NYI");
return;
}
}
else {
setPropertyFn = CGM.getObjCRuntime().GetPropertySetFunction();
if (!setPropertyFn) {
CGM.ErrorUnsupported(propImpl, "Obj-C setter requiring atomic copy");
return;
}
}
}

这里是个switch语句,调用的条件取决于strategy.getKind(),我们接下来就搜索PropertyImplStrategy找一下类型是什么时候设置的,在PropertyImplStrategy的定义中我们找到了答案:copy修饰的属性会有Kind = GetSetProperty,也就是set方法会调用objc_setProperty

1
2
3
4
5
6
// If we have a copy property, we always have to use getProperty/setProperty.
// TODO: we could actually use setProperty and an expression for non-atomics.
if (IsCopy) {
Kind = GetSetProperty;
return;
}

我们写代码验证一下:

1
2
3
4
@property (nonatomic, copy) NSString *nickName;
@property (atomic, copy) NSString *acnickName;
@property (nonatomic) NSString *nnickName;
@property (atomic) NSString *anickName;

我们定义四个属性,然后用clang将其转换成c++代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static NSString * _I_JSPerson_nickName(JSPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_JSPerson$_nickName)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_JSPerson_setNickName_(JSPerson * self, SEL _cmd, NSString *nickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct JSPerson, _nickName), (id)nickName, 0, 1); }//copy

extern "C" __declspec(dllimport) id objc_getProperty(id, SEL, long, bool);

static NSString * _I_JSPerson_acnickName(JSPerson * self, SEL _cmd) { typedef NSString * _TYPE;
return (_TYPE)objc_getProperty(self, _cmd, __OFFSETOFIVAR__(struct JSPerson, _acnickName), 1); }
static void _I_JSPerson_setAcnickName_(JSPerson * self, SEL _cmd, NSString *acnickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct JSPerson, _acnickName), (id)acnickName, 1, 1); }//copy

static NSString * _I_JSPerson_nnickName(JSPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_JSPerson$_nnickName)); }
static void _I_JSPerson_setNnickName_(JSPerson * self, SEL _cmd, NSString *nnickName) { (*(NSString **)((char *)self + OBJC_IVAR_$_JSPerson$_nnickName)) = nnickName; }//strong

static NSString * _I_JSPerson_anickName(JSPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_JSPerson$_anickName)); }
static void _I_JSPerson_setAnickName_(JSPerson * self, SEL _cmd, NSString *anickName) { (*(NSString **)((char *)self + OBJC_IVAR_$_JSPerson$_anickName)) = anickName; }//strong

小结

  • set方法在底层并不是定义了很多set方法调用,而是采用内存平移或调用objc_setProperty方法。
  • 使用copy修饰的属性的set方法调用的是objc_setProperty方法。
  • 没有copy修饰的属性的set方法是内存平移

isKindOfClassvsisMemberOfClass

我们平时开发经常会使用isKindOfClassisMemberOfClass方法来判断对象的类型,从一个例子开始。首先定义两个继承关系的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@interface JSPerson : NSObject{
NSString *hobby;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic) int age;
@end
@implementation JSPerson
@end
@interface JSTeacher : JSPerson
@property (nonatomic, copy) NSString *hobby;
- (void)teacherSay;
@end
@implementation JSTeacher
- (void)teacherSay{
NSLog(@"%s",__func__);
}
@end

我们定义一个方法,用来打印方法的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
void jsKindofDemo(void){
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; //
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; //
BOOL re3 = [(id)[JSPerson class] isKindOfClass:[JSPerson class]]; //
BOOL re4 = [(id)[JSPerson class] isMemberOfClass:[JSPerson class]]; //
NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);

BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]]; //
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]]; //
BOOL re7 = [(id)[JSPerson alloc] isKindOfClass:[JSPerson class]]; //
BOOL re8 = [(id)[JSPerson alloc] isMemberOfClass:[JSPerson class]]; //
NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
}

打印结果如下:

1
2
3
4
5
6
7
8
re1 :1
re2 :0
re3 :0
re4 :0
re5 :1
re6 :1
re7 :1
re8 :1

re5-re8符合我们平时使用的思想,对象判断是否是类,re1-re4是什么情况呢,我们看isKindOfClass底层源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Calls [obj isKindOfClass]
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__//我们只看这里 objc2
if (slowpath(!obj)) return NO;
Class cls = obj->getIsa();//获取对象(类对象)的类(元类)
if (fastpath(!cls->hasCustomCore())) {
for (Class tcls = cls; tcls; tcls = tcls->getSuperclass()) {//遍历父类
if (tcls == otherClass) return YES;
}
return NO;
}
#endif
return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}

通过源码和上一篇中isa的走位和继承链,我们可以看出

  • 实例对象调用:1、获取对象的类。2、依次查找对象的类和父类,如果和传入的类相等返回YES,遍历结束未找到返回NO

    即查找顺序:对象的类->父类->根类(NSObject)->nil

  • 类对象:1、获取到类的元类。2、依次查找元类及元类的父类,如果和传入的类相等返回YES,遍历结束未找到返回NO

    即查找顺序:元类->元类父类->根元类->根类(NSObject)->nil

通过上面的结论,我们看上面的例子:

  • re1:传入是NSObject的类对象,首先找它的元类即根元类,根元类的父类是NSObject=传入的第二个参数,所以re1=1
  • re3:传入是JSPerson的类对象,首先找它的元类,依次找元类的父类到根元类,最后到根类,没有类=[JSPerson class],所以re3=0
  • re5:传入是NSObject的实例对象,找它的类就是NSObject=[NSObject class],所以re5=1
  • re7:传入是JSPerson的实例对象,找它的类就是JSPerson=[JSPerson class],所以re7=1

我们继续看isMemberOfClass的源码

1
2
3
4
5
6
7
8
//类方法
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
//实例方法
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}

可以看出:

  • 实例对象:判断实例对象的类和传入的类是否相等,即是否相等
  • 类对象:判断类的元类是否和传入的类相等,即看元类

通过上面结论我们继续看例子:

  • re2:传入是NSObject的类对象,首先找它的元类即根元类,根元类不是NSObject类,所以re2=0
  • re4:传入是JSPerson的类对象,首先找它的元类,元类!=[JSPerson class],所以re4=0
  • re6:传入是NSObject的实例对象,找它的类就是NSObject=[NSObject class],所以re6=1
  • re8:传入是JSPerson的实例对象,找它的类就是JSPerson=[JSPerson class],所以re8=1

本文我们主要研究了类的rorw的区别,属性的set方法,以及isKindOfClassvsisMemberOfClass的源码,类的探究就到这里了,有遗漏的话后面会补充。