在本篇文章中,我们一起来探索类的结构
。
分析
在runtime
中我们可以通过object_getClass
方法获取实例对象
的类信息
LYPerson *person = [[LYPerson alloc] init];
Class p = object_getClass(person)
我们来看下 Class
的实现
typedef struct objc_class *Class;
struct objc_class : objc_object {
// Class ISA; // 1
Class superclass; // 2
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() const { //3
return bits.data();
}
}
- 1,继承自
objc_object
默认都会有isa
指针。 - 2,
superclass
:其父类信息。 - 3,
class_rw_t
: 类的具体分析。
从 对objc_class
整体的探索我们可以看到,类对象
的第一部分由 isa指针
组成。我们先来分析isa
指向的问题
isa指向
在之前的文章 isa结构分析 一文中,我们分析了 isa指针
的结构组成。如果对其结构不熟悉,可以进行查看。
我们先初始化一个实例对象person, person继承于 NSObject
:
LYPerson *person = [[LYPerson alloc] init];
1,我们先查看下实例对象person
的内存分布
(lldb) x/4gx person // 1 打印类的内存分布
0x10061eca0: 0x001d8001000021b9 0x0000000000000000
0x10061ecb0: 0x646e6946534e5b2d 0x466e726574746150
2,通过ISA_MASK
获取类信息
(lldb) p/x 0x00007ffffffffff8ULL & 0x001d8001000021b9 // 获取类信息
(unsigned long long) $2 = 0x00000001000021b8
(lldb) po 0x00000001000021b8 // 打印类信息
LYPerson
⚠️我们可以看到 实例对象person的isa指针
指向 LYPerson
3,我们直接查看LYPerson.class
的内存分布来进行验证
(lldb) x 0x00000001000021b8 // 查看类信息的内存分布
0x1000021b8: e0 21 00 00 01 00 00 00 40 11 3f 00 01 00 00 00 .!......@.?.....
0x1000021c8: 20 f4 61 00 01 00 00 00 03 00 00 00 10 80 01 00 .a.............
(lldb) x LYPerson.class //直接查看 `LYPerson.class`的内存分布
0x1000021b8: e0 21 00 00 01 00 00 00 40 11 3f 00 01 00 00 00 .!......@.?.....
0x1000021c8: 20 f4 61 00 01 00 00 00 03 00 00 00 10 80 01 00 .a.............
我们可以看出 它们两个的内存空间是一样的
,这就说明了实例对象的isa
指向 类对象(Class)
。
4,我们继续查看LYPerson类对象
的内存分布
(lldb) x/4gx LYPerson.class // 1,获取 Person的类信息
0x1000021b8: 0x00000001000021e0 0x00000001003f1140
0x1000021c8: 0x00000001014261b0 0x000680100000000f
我们可以看出 类对象的isa指向另一个类
。
5,通过ISA_MASK
得到类对象的指向
(lldb) p/x 0x00000001000021e0 & 0x00007ffffffffff8ULL // 2,与上ISA_Mask得到类信息,
// 得到类对象的isa,就得到类对象的指向元类
(unsigned long long) $12 = 0x00000001000021e0
⚠️ 这样我们就看出 类对象的isa
指向元类
。
6,查看 元类
的内存分布,我们可以发现,元类也是一个类
,也有isa
(lldb) x/4gx 0x00000001000021e0 // 3,查看元类的内存分布
0x1000021e0: 0x00000001003f10f0 0x00000001003f10f0
0x1000021f0: 0x000000010061f460 0x0005e03100000007
紧接着我们看下元类对象指向哪个类
(lldb) p/x 0x00000001003f10f0 & 0x00007ffffffffff8ULL // 查看 元类的指向
(unsigned long long) $13 = 0x00000001003f10f0
(lldb) po 0x00000001003f10f0 // 查看 元类 isa指向的类信息值
NSObject
⚠️ 原来,person
对象的元类
的isa
指向 NSObject
。
7,我们接着查看NSObject
的内存分布
(lldb) x/4gx 0x00000001003f10f0 // 查看 NSObjec 的内存分布
0x1003f10f0: 0x00000001003f10f0 0x00000001003f1140
0x1003f1100: 0x0000000101406a80 0x0004e03100000007
⚠️ NSObjec
的isa
指向NSObjec本身
小结
-
实例对象
的isa
指向类对象
,类对象
的isa
指向元类对象
,元类对象
的isa
指向NSObject
,NSObject
的isa
指向NSObject
本身。 -
isa
代表一种归属关系,实例对象
的归属
是类对象
,类对象
的归属
是元类对象
,元类对象
的归属是NSObjec
,元类
的定义
和创建
都是由编译器自动创建的
。
类的继承关系
我们新建一个LYTeacher
类,LYPerson
类 继承 于 LYTeacher
int main(int argc, const char * argv[]) {
@autoreleasepool {
LYPerson *person = [[LYPerson alloc] init];
LYTeacher *teacher = [[LYTeacher alloc] init];
NSObject *obj = [[NSObject alloc] init];
NSLog(@"%@", person.superclass);
NSLog(@"%@", teacher.superclass);
NSLog(@"%@", obj.superclass);
}
return 0;
}
其输出结果如下
2020-09-13 14:19:49.968665+0800 001[74326:2492359] LYTeacher
2020-09-13 14:19:49.969259+0800 001[74326:2492359] NSObject
2020-09-13 14:19:49.969316+0800 001[74326:2492359] (null)
我们可以看到 实例对象person
的父类
是 LYTeacher类对象
,实例对象teacher
的父类
是NSObject类对象
,实例对象obj
的父类
是nil
。
⚠️OC对象的继承关系源于类,实例对象是没有继承关系的
综上所述,isa的指向
和 类对象的继承
可概括为下图
class_rw_t
属性的存储
在文章的开头部分,我们分析了 objc_class
的整体结构,包含8字节的isa
,8字节的superclass
和 16字节的cache
。接下来我们分析类
的property
和method
是存在哪里的?
1,我们给LYPerson
类新增一个name
,height
属性和 -(void)sayHello
方法
@interface LYPerson : NSObject
{
NSInteger height;
}
@property(nonatomic, copy) NSString *name;
@property(nonatomic, copy) NSString *nickName;
-(void)sayHello;
+(void)say666;
@end
@implementation LYPerson
-(void)sayHello{
NSLog(@"hello");
}
+(void)say666 {
NSLog(@"6666");
}
@end
2,新建LYPerson
类,并打印其类对象
内存地址
(lldb) p/x LYPerson.class
(Class) $0 = 0x0000000100002338 LYPerson
3,class_data_bits_t bits
属性在结构体内偏移32
位置存放(0~7
: isa
,8~15
:superclass
,16 ~ 31
: cache
),我们通过首地址 + 偏移值
得到 bits
的值。
(lldb) p/x 0x0000000100002338 + 0x20 // 1
(long) $2 = 0x0000000100002358
(lldb) p (class_data_bits_t *)$2 // 2
(class_data_bits_t *) $3 = 0x0000000100002358
(lldb) p *$3 // 3
(class_data_bits_t) $4 = (bits = 4301859572)
- 1,计算
class_data_bits_t bits
的实际地址值。 - 2,输出
bits
。 - 3,读取内存值。
4,获取 class_rw_t *data()
的值
(lldb) p $4.data()
(class_rw_t *) $6 = 0x0000000100692af0
(lldb) p *$6
(class_rw_t) $7 = {
flags = 2148007936
witness = 0
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = 4294975792
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
5,从 class_rw_t
中获取 properties
(lldb) p $7.properties()
(const property_array_t) $8 = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x0000000100002278
arrayAndFlag = 4294976120
}
}
}
6,获取list
数组
(lldb) p $8.list
(property_list_t *const) $9 = 0x0000000100002278
7,读取 list
数组里面的值
(lldb) p *$9
(property_list_t) $10 = {
entsize_list_tt<property_t, property_list_t, 0> = {
entsizeAndFlags = 16
count = 2 // 1
first = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
}
}
- 1,
count = 2
:表示一共有2个属性。
8,我们依次获取属性值
(lldb) p $10.get(0) // 1
(property_t) $11 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
(lldb) p $10.get(1) // 2
(property_t) $12 = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
(lldb) p $10.get(2) //3
Assertion failed: (i < count), function get, file /Users/ritamashin/Desktop/可编译objc源码/runtime/objc-runtime-new.h, line 438.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
- 1,2, :依次读取第一个属性和第二个属性。
- 3,当读取第3个属性时,数组越界报错,但我们在类里面定义了
2个属性和1个成员变量
,成员变量去哪里了???。
成员变量的存储
我们对源码进行分析,在class_ro_t
中有const ivar_list_t * ivars
,我们通过 class_rw_t
找到 class_ro_t
,然后对 ivars 进行查看。
(lldb) p $7.ro() \\ 1
(const class_ro_t *) $26 = 0x0000000100002130
- 1,$7 是
class_rw_t
结构体,调用ro()
方法,得到class_ro_t
对象。
(lldb) p $26->ivars \\1
(const ivar_list_t *const) $28 = 0x0000000100002210
(lldb) p $28
(const ivar_list_t *const) $28 = 0x0000000100002210
(lldb) p *$28 \\2
(const ivar_list_t) $29 = {
entsize_list_tt<ivar_t, ivar_list_t, 0> = {
entsizeAndFlags = 32
count = 3
first = {
offset = 0x00000001000022a8
name = 0x0000000100000f2d "height"
type = 0x0000000100000f87 "q"
alignment_raw = 3
size = 8
}
}
}
- 得到ivars的值,得到
ivars_list
对象。
(lldb) p $29.get(0)
(ivar_t) $30 = {
offset = 0x00000001000022a8
name = 0x0000000100000f2d "height"
type = 0x0000000100000f87 "q"
alignment_raw = 3
size = 8
}
(lldb) p $29.get(1)
(ivar_t) $31 = {
offset = 0x00000001000022b0
name = 0x0000000100000f34 "_name"
type = 0x0000000100000f89 "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $29.get(2)
(ivar_t) $32 = {
offset = 0x00000001000022b8
name = 0x0000000100000f3a "_nickName"
type = 0x0000000100000f89 "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $29.get(3)
Assertion failed: (i < count), function get, file /Users/ritamashin/Desktop/可编译objc源码/runtime/objc-runtime-new.h, line 438.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
- 依次读取
ivar_list
的值,其值依次为height
,_name
,_nickName
小计
由此我们可以得出,属性
存放在 properties
里面,成员变量
和@property属性
生成的成员变量都存放在 ivars
里面。
Methods
实例方法 (instance method)
9,获取 该类的方法列表
(lldb) p $7.methods()
(const method_array_t) $13 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100002178
arrayAndFlag = 4294975864
}
}
}
10,获取 method list
(lldb) p $13.list
(method_list_t *const) $14 = 0x0000000100002178
(lldb) p *$14
(method_list_t) $15 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 6
first = {
name = "sayHello"
types = 0x0000000100000f7f "v16@0:8"
imp = 0x0000000100000d10 (KCObjc`-[LYPerson sayHello])
}
}
}
11,依次获取method list
里面的值
(lldb) p $15.get(0)
(method_t) $16 = {
name = "sayHello" // 1
types = 0x0000000100000f7f "v16@0:8"
imp = 0x0000000100000d10 (KCObjc`-[LYPerson sayHello])
}
(lldb) p $15.get(1)
(method_t) $17 = {
name = ".cxx_destruct" // 2
types = 0x0000000100000f7f "v16@0:8"
imp = 0x0000000100000d40 (KCObjc`-[LYPerson .cxx_destruct])
}
(lldb) p $15.get(2)
(method_t) $18 = {
name = "name" // 3
types = 0x0000000100000f95 "@16@0:8"
imp = 0x0000000100000d80 (KCObjc`-[LYPerson name])
}
(lldb) p $15.get(3)
(method_t) $19 = {
name = "setName:" // 4
types = 0x0000000100000f9d "v24@0:8@16"
imp = 0x0000000100000db0 (KCObjc`-[LYPerson setName:])
}
(lldb) p $15.get(4)
(method_t) $20 = {
name = "setNickName:" //5
types = 0x0000000100000f9d "v24@0:8@16"
imp = 0x0000000100000e10 (KCObjc`-[LYPerson setNickName:])
}
(lldb) p $15.get(5)
(method_t) $21 = {
name = "nickName" //6
types = 0x0000000100000f95 "@16@0:8"
imp = 0x0000000100000de0 (KCObjc`-[LYPerson nickName])
}
(lldb) p $15.get(6) // 7
Assertion failed: (i < count), function get, file /Users/ritamashin/Desktop/可编译objc源码/runtime/objc-runtime-new.h, line 438.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
- 1,实例方法
sayHello
。 - 2,类的析构方法。
- 3,
name
属性的get
方法。 - 4,
name
属性的set
方法。 - 5,
nickName
的set
方法。 - 6,
nickName
的get
方法。 - 7,当我们试图查找
类方法 say666
时,造成了数组越界报错了。
我们在 method_list
里面只找到了对象的实例方法
,那类方法
存在哪里呢?
在文章的第一部分我们提到 isa
代表一种归属关系,实例对象
的 归属
是 类对象
,类对象
的归属
是 元类对象
,元类对象
的归属是 NSObject
。类对象里面存放了实例方法,那我们试着在 元类对象
里面去查找类方法
。
类方法(class method)
(lldb) p/x LYPerson.class \\ 1
(Class) $0 = 0x0000000100002338 LYPerson
(lldb) x/4gx 0x0000000100002338 \\2
0x100002338: 0x0000000100002310 0x00000001003f0140
0x100002348: 0x00000001003ea450 0x0000802c00000000
- 1,输出 LYPerson类对象的首地址。
- 2,查看
LYPerson类对象
的内存分布
,得到LYPerson类对象的元类
地址0x0000000100002310
(lldb) p/x 0x0000000100002310 + 0x20 // 1
(long) $1 = 0x0000000100002330
(lldb) p (class_data_bits_t *)$1
(class_data_bits_t *) $2 = 0x0000000100002330
- 1,获取
class_data_bits_t
的首地址
(lldb) p $2-> data() \\1
(class_rw_t *) $3 = 0x00000001010180f0
(lldb) p $3.methods() \\2
(const method_array_t) $4 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100002110
arrayAndFlag = 4294975760
}
}
}
Fix-it applied, fixed expression was:
$3->methods()
- 1,得到
class_rw_t
对象。 - 2,获得其
methods
(lldb) p $4.list \\1
(method_list_t *const) $5 = 0x0000000100002110
(lldb) p *$5
(method_list_t) $6 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 1
first = {
name = "say666"
types = 0x0000000100000f7f "v16@0:8"
imp = 0x0000000100000ce0 (KCObjc`+[LYPerson say666])
}
}
}
- 1,得到
method_list_t
对象
(lldb) p $6.get(0)
(method_t) $7 = {
name = "say666"
types = 0x0000000100000f7f "v16@0:8"
imp = 0x0000000100000ce0 (KCObjc`+[LYPerson say666])
}
(lldb) p $6.get(1)
Assertion failed: (i < count), function get, file /Users/ritamashin/Desktop/可编译objc源码/runtime/objc-runtime-new.h, line 438.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
- 依次获取
method_list
里面的值,我们可以看到类方法存在于元类对象的method_list
里面。
小计:在这里我们从class_rw_t
中找到了,LYPerson
类对象里面的方法列表
,属性列表
,成员变量ivars
。从此可见,类对象
包含了isa指针
,superClass类对象
,属性列表
和方法列表
,实例方法存在类对象的method_list里面,类方法存在元类的method_list里面
总结:
我们在这篇文章中,我们探讨了 isa指针的指向
,对象之间的继承关系
和 类对象的组成结构
。