我们都知道类是Class类型
的,那么Class
究竟是什么,里面存放着什么信息呢?今天我们通过objc的源码来看下Class究竟是什么。
在objc.h文件
的一开始我们就找到了关于Class的定义
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
由定义可以看到Class就是指向objc_class类型变量的一个指针
。那么objc_class
又是什么呢?
// objc_class的定义仅节选了属性部分,全部源码请自行下载objc源码查看。
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
}
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
从上述代码可知objc_class
是一个继承自objc_object
的一个结构体,内部有4个变量
- Class类型的ISA:ISA是由objc_object结构体继承过来的,其中存储的是类的信息,具体情况可以查看03 isa探究。
- Class类型的superclass:superclass指向父类,存储了Class的继承关系。
- cache_t类型的cache:cache是方法缓存列表,存储了最近调用的方法。
- class_data_bits_t类型的bits:bits中存储了类的详细信息,包括成员变量,属性,方法等。
objc_object
就是一个简单的结构体,内部只有一个Class类型的变量
,其他什么都没有。
首先,由上面objc_class的定义
就知道,objc_class
的内部有一个isa指针
,而我们又知道isa
其实是实例对象指向自己类的一个指针
,也就证明了其实Class本身也是一个实例对象
,Class也有归属的类,这种类我们称为元类
。
我们今天的分析就从类的isa的走向
和superclass的走向
开始。为此我们准备两个类:SLPerson
和继承SLPerson的SLTeacher
。
首先我们分析isa的走向:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// ISA_MASK 0x00007ffffffffff8ULL
SLTeacher *teacher = [SLTeacher alloc];
NSLog(@"");
}
return 0;
}
我们在NSLog处打上断点,使用lldb调试指令
(lldb) x/4gx teacher // 打印teacher的内存分布,第一个8个字节代表的其isa指针
0x101274ef0: 0x011d80010000838d 0x0000000000000000
0x101274f00: 0x0000000000000000 0x0000000000000000
(lldb) p 0x011d80010000838d & 0x00007ffffffffff8ULL // 我们将teacher的isa 和 ISA_MASK进行&运算
(unsigned long long) $9 = 4295000968 // 打印结果得到SLTeacher类
(lldb) po $9
SLTeacher
(lldb) x/4gx $9 // 接下来打印SLTeacher类的内存分布,第一个8个字节代表的其isa指针
0x100008388: 0x0000000100008360 0x0000000100008338
0x100008398: 0x000000010174b240 0x0002802c00000003
(lldb) p 0x0000000100008360 & 0x00007ffffffffff8ULL // 我们将SLTeacher类的isa 和 ISA_MASK进行&运算
(unsigned long long) $10 = 4295000928
(lldb) po $10 // 打印结果得到SLTeacher元类
SLTeacher
(lldb) x/4gx $10 // 接下来打印SLTeacher元类的内存分布,第一个8个字节代表的其isa指针
0x100008360: 0x000000010036a0f0 0x0000000100008310
0x100008370: 0x00000001016057b0 0x0002e03500000003
(lldb) p 0x000000010036a0f0 & 0x00007ffffffffff8ULL // 我们将SLTeacher元类的isa 和 ISA_MASK进行&运算
(unsigned long long) $11 = 4298547440
(lldb) po $11 // 打印结果得到NSObject根元类
NSObject
(lldb) x/4gx $11 // 接下来打印NSObject根元类的内存分布,第一个8个字节代表的其isa指针
0x10036a0f0: 0x000000010036a0f0 0x000000010036a140
0x10036a100: 0x000000010127eea0 0x0003e03100000007
(lldb) p 0x000000010036a0f0 & 0x00007ffffffffff8ULL // 我们将NSObject根元类的isa 和 ISA_MASK进行&运算
(unsigned long long) $12 = 4298547440
(lldb) po $12 // 打印结果还是得到NSObject根元类
NSObject
接着我们分析superclass的走向
// 首先我们打印NSObject根元类的superclass,我们知道superclass位与类的内存分布的第二个8个字节,即x/4gx结果的第二个内存地址
(lldb) po 0x000000010036a140 // 打印得到NSObject根元类的superclass还是NSObject,那这个NSObject到底是根元类,还是根类?
NSObject
(lldb) x/4gx 0x000000010036a140 // 我们接着打印0x000000010036a140的内存分布
// 可以看到此NSObject的isa又指向了NSObject根源类,同时此NSObject的superclass为nil,由此可知此为NSObject根类
0x10036a140: 0x000000010036a0f0 0x0000000000000000
0x10036a150: 0x00000001012767e0 0x0002801000000003
(lldb) po 0x0000000100008338 // 同时我们打印SLTeacher类的superclass是SLPerson类
SLPerson
(lldb) x/4gx 0x0000000100008338 // 再打印SLPerson类的内存分布,得到SLPerson类的superclass是NSObject根类
0x100008338: 0x0000000100008310 0x000000010036a140
0x100008348: 0x0000000100362370 0x0000802400000000
由上面的lldb调试结果,我们可以验证苹果一副非常经典的superclass和isa走位图
。下图的红色线条部分
我们已经证明
了,黑色线条部分大家感兴趣的可以自己证明下。
分析了类的isa和superclass
之后我们先不看cache
,这是一个方法缓存列表
,为了加快方法的查找速度的,其实有或者没有都不会影响程序的运行。我们都知道类中除了isa和superclass信息之外还有很多我们申明的属性,成员变量以及方法
,那么这些信息存储在什么地方呢?因为objc_class一共就只有4个变量,因此剩下的这些信息就只能存储在bits中。
我们先来看下bits
变量,它是class_data_bits_t类型
的,我们来到class_data_bits_t
的定义。其中有一个uintptr_t
类型的变量bits
,同时有一个data函数
可以获取bits中的相关信息
,返回class_rw_t类型的指针
。
struct class_data_bits_t {
// ...
// Values are the FAST_ flags above.
uintptr_t bits;
// ...
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
};
在class_rw_t
结构体的定义中我们看到了几个函数,分别是
- 获取
class_ro_t
类型指针的函数ro()
,在class_ro_t的定义中我们也看到了ivars(成员变量),以及属性列表,方法列表和协议列表; - 获取
方法列表
的函数methods()
; - 获取
属性列表
的函数properties()
; - 获取
协议列表
的函数protocols()
;
struct class_rw_t {
// ...
const class_ro_t *ro() const {
auto v = get_ro_or_rwe();
if (slowpath(v.is<class_rw_ext_t *>())) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
}
return v.get<const class_ro_t *>(&ro_or_rw_ext);
}
// ...
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};
}
}
// ...
};
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;
// 方法列表
void *baseMethodList;
// 协议列表
protocol_list_t * baseProtocols;
// 成员变量
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
// 属性列表
property_list_t *baseProperties;
};
为此我们在SLPerson中添加一些成员变量,属性和方法。
@interface SLPerson : NSObject
{
int height;
double weight;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
- (void)sayHello;
- (void)sayGoodBye;
+ (void)say666;
@end
然后我们还是将程序运行起来,将程序断点到NSLog的地方,因为这是底层代码的原因,所以之前的所有变量我们都是通过取内存地址
的方式来取值,bits变量我们也是通过这种方式取值,isa和superclass都是8个字节,cache_t的大小在iOS上是16字节
(计算细节不讲了,感兴趣的可以私信),所以bits变量位于首地址偏移32字节的位置
,即首地址的倒数第二位+2。接下来我们还是要通过lldb进行调试~
(lldb) x/8gx SLPerson.class
0x1000083c0: 0x0000000100008398 0x000000010036a140
0x1000083d0: 0x0000000100362370 0x0000803400000000
0x1000083e0: 0x000000010152f4e4 0x000000010036a0f0
0x1000083f0: 0x0000000100008398 0x0000000100362370
(lldb) p (class_data_bits_t *)0x1000083e0
(class_data_bits_t *) $1 = 0x00000001000083e0
(lldb) p $1->data()
(class_rw_t *) $2 = 0x000000010152f4e0
通过上面的lldb调试我们获取到了class_rw_t的指针,接下来我们先获取属性列表
(lldb) p $2.properties()
(const property_array_t) $3 = {
list_array_tt<property_t, property_list_t, RawPtr> = {
= {
list = {
ptr = 0x0000000100008220
}
arrayAndFlag = 4295000608
}
}
}
Fix-it applied, fixed expression was:
$2->properties()
(lldb) p $3.begin()
(list_array_tt<property_t, property_list_t, RawPtr>::iterator) $4 = {
lists = 0x00000001005e47f0
listsEnd = 0x00000001005e47f8
m = {
entsize = 16
index = 0
element = 0x0000000100008228
}
mEnd = {
entsize = 16
index = 2
element = 0x0000000100008248
}
}
(lldb) p (property_t *)0x0000000100008228
(property_t *) $5 = 0x0000000100008228
(lldb) p $5[0]
(property_t) $6 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
(lldb) p $5[1]
(property_t) $7 = (name = "age", attributes = "Ti,N,V_age")
(lldb) p $5[2]
(property_t) $8 = (name = "", attributes = "")
我们通过properties()
函数成功拿到了SLPerson
的两个属性name和age
。看看我们能不能拿到成员变量呢。
(lldb) p $2->ro()
(const class_ro_t *) $9 = 0x00000001000080a0
(lldb) p $9->ivars
(const ivar_list_t *const) $10 = 0x0000000100008198
(lldb) p $10->begin()
(const entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop>::iterator) $11 = {
entsize = 32
index = 0
element = 0x00000001000081a0
}
(lldb) p (ivar_t *)0x00000001000081a0
(ivar_t *) $12 = 0x00000001000081a0
(lldb) p $12[0]
(ivar_t) $13 = {
offset = 0x0000000100008370
name = 0x0000000100003ee6 "height"
type = 0x0000000100003f73 "i"
alignment_raw = 2
size = 4
}
(lldb) p $12[1]
(ivar_t) $14 = {
offset = 0x0000000100008378
name = 0x0000000100003eed "weight"
type = 0x0000000100003f75 "d"
alignment_raw = 3
size = 8
}
(lldb) p $12[2]
(ivar_t) $15 = {
offset = 0x0000000100008380
name = 0x0000000100003ef4 "_age"
type = 0x0000000100003f73 "i"
alignment_raw = 2
size = 4
}
(lldb) p $12[3]
(ivar_t) $16 = {
offset = 0x0000000100008388
name = 0x0000000100003ef9 "_name"
type = 0x0000000100003f77 "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $12[4]
(ivar_t) $17 = {
offset = 0x0000000200000010
name = 0x0000000100003e83 "name"
type = 0x0000000100003e88 "T@\"NSString\",C,N,V_name"
alignment_raw = 16032
size = 1
}
(lldb) p $12[5]
(ivar_t) $18 = {
offset = 0x0000000100003ea4
name = 0x0000002800000081 ""
type = 0x0000000000000028 ""
alignment_raw = 0
size = 0
}
我们通过ivars的属性拿到了成员变量的列表
,但是其中name多出一个定义
,暂时还不知道是什么原因,方法列表的获取咱们后面再讲。