OC底层原理汇总
在上一篇中,我们对isa
的初始化、类与对象的底层结构以及属性进行了简单剥析。
- 对于
isa
,我们得出结论,isa
是一个存储了所属类的地址; - 对于类的底层结构,我们得出了类有四个成员,
isa
、superclass
、cache
、bits
- 对于属性,我们得出了属性是由
实例变量
+setter
+getter
组成。
在这一篇中,我们将进一步探析:
-
isa
指向的类的isa
指向什么呢,如果一直指向下去,最终会指向哪里? - 对于
cache
、bits
,它们分别是用来做什么的呢?它们内部结构是什么样的呢?
我们接下来逐一进行分析
一、isa
指向
1.使用lldb
命令分析isa
指向
我们在main.m
中,首先创建一个LWPerson
类继承自NSObject
,然后再创建一个LWStudent
继承自LWPerson
,并在main
函数中创建两个类的实例,在创建后打一个断点,如下图所示:
在上一篇中我们知道,isa.bits & ISA_MASK
就可以获得isa
指向的类的地址,据此,我们使用lldb
来调试查找isa
的走位,如下面代码所示
//16进制打印student的地址
(lldb) p/x student
(LWStudent *) $0 = 0x0000000100546690
//根据student的内容连续打印四段,第一段就是isa.bits
(lldb) x/4gx $0
0x100546690: 0x001d8001000023d5 0x00000000003c0001
0x1005466a0: 0x0000000100001030 0x0000000000000457
//得到isa指向的类的地址
(lldb) p/x 0x001d8001000023d5 & 0x00007ffffffffff8ULL
(unsigned long long) $1 = 0x00000001000023d0
//po之后,可以看到指向LWStudent
(lldb) po $1
LWStudent
//继续找LWStudent的isa指向
(lldb) x/4gx $1
0x1000023d0: 0x00000001000023a8 0x0000000100002380
0x1000023e0: 0x00000001005466f0 0x0002802c00000007
(lldb) p/x 0x00000001000023a8 & 0x00007ffffffffff8ULL
(unsigned long long) $2 = 0x00000001000023a8
//po 之后发现,也是指向LWStudent
(lldb) po $2
LWStudent
//继续查找isa指向
(lldb) x/4gx $2
0x1000023a8: 0x00007fff991e60f0 0x0000000100002358
0x1000023b8: 0x0000000100546770 0x0003e03500000007
(lldb) p/x 0x00007fff991e60f0 & 0x00007ffffffffff8ULL
(unsigned long long) $3 = 0x00007fff991e60f0
//此处发现指向NSObject
(lldb) po $3
NSObject
//继续查找NSObject的isa指向
(lldb) x/4gx $3
0x7fff991e60f0: 0x00007fff991e60f0 0x00007fff991e6118
0x7fff991e6100: 0x0000000100704fb0 0x0004e03100000007
(lldb) p/x 0x00007fff991e60f0 & 0x00007ffffffffff8ULL
(unsigned long long) $4 = 0x00007fff991e60f0
(lldb) po $4
//发现,NSObject的isa还是指向NSObject
NSObject
//我们继续查找
(lldb) x/4gx $4
0x7fff991e60f0: 0x00007fff991e60f0 0x00007fff991e6118
0x7fff991e6100: 0x0000000100704fb0 0x0004e03100000007
(lldb) p/x 0x00007fff991e60f0 & 0x00007ffffffffff8ULL
(unsigned long long) $5 = 0x00007fff991e60f0
(lldb) po $5
//发现仍然是NSObject,并且地址都是0x00007fff991e60f0
NSObject
上方的lldb
指令大致流程就是从查找student
的isa
指向出发,不断的查找指向的指向,其大致流程图如下
[图片上传失败...(image-e690ab-1600595328194)]
从案例中我们可以发现,对象student
的isa
指向Student
,而Student
的isa
也是指向的Student
,Student
的isa
指向NSObject
,而NSObject
的isa
指向自己。
在这里,连续出现了两个Student
,这两个的地址相差40
个字节,并不是同一个,为什么会出现两个不同的Student
呢?
为了分析这个问题,我们使用lldb
命令,新创建一个NSObject
类的对象,继续查找是否会出现两个NSObject
,如下所示
//创建一个NSObject的对象
(lldb) expr [NSObject alloc]
(NSObject *) $6 = 0x000000010071b990
(lldb) po $6
(lldb) x/4gx $6
0x10071b990: 0x001dffff991e6119 0x0000000000000000
0x10071b9a0: 0x656b6f54534e5b2d 0x7420646c6569466e
(lldb) p/x 0x001dffff991e6119 & 0x00007ffffffffff8ULL
//找到它的指向,地址为0x00007fff991e6118
(unsigned long long) $7 = 0x00007fff991e6118
(lldb) po $7
NSObject
//继续查找NSObject的isa指向
(lldb) x/4gx $7
0x7fff991e6118: 0x00007fff991e60f0 0x0000000000000000
0x7fff991e6128: 0x0000000100630340 0x0004801000000007
(lldb) p/x 0x00007fff991e60f0 & 0x00007ffffffffff8ULL
//找到NSObject的isa指向,地址为0x00007fff991e60f0
(unsigned long long) $8 = 0x00007fff991e60f0
(lldb) po $8
NSObject
测试后可以看出,NSObject
也有两个,两者的地址相差也是40
个字节,其最终指向的NSObject
的地址为0x00007fff991e60f0
,与我们之前指令得到的最终指向NSObject
的地址相同。
2.通过调用runtime API来分析isa
走向
我们在main
函数中编辑代码如下:
int main(int argc, const char * argv[]) {
LWStudent *student = [LWStudent alloc];
Class class1 = object_getClass(student);
NSLog(@"class1 is %@",class1);
Class class2 = object_getClass(class1);
NSLog(@"class2 is %@",class2);
Class class3 = object_getClass(class2);
NSLog(@"class3 is %@",class3);
Class class4 = object_getClass(class3);
NSLog(@"class4 is %@",class4);
Class class5 = object_getClass(class4);
NSLog(@"class5 is %@",class5);
return 0;
}
//打印结果
class1 is LWStudent
class2 is LWStudent
class3 is NSObject
class4 is NSObject
class5 is NSObject
结果与lldb
调试得到的结果一致
3.isa
指向图、类对象、元类
综合以上分析,我们得到了这张经典图
在之前的测试中,我们得到两个类分别称之为类对象与元类,类对象是元类的对象,而NSObject的元类我们称之为根元类。
这时,isa
的指向我们可以总结为:对象
->类对象
->元类
->根元类
->根元类
。根元类
继承于NSObject
,它的isa
指向自身。
4.分析类结构体struct objc_class
结构
同时,根据我们之前的测试,类对象的地址比它的元类高40
字节,我们根据struct objc_class
的结构进行分析。
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
class_rw_t *data() const {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
}
其中cache_t
是一个结构体,它内部所有的成员变量如下:
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic _buckets;
explicit_atomic _mask;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic _maskAndBuckets;
mask_t _mask_unused;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
explicit_atomic _maskAndBuckets;
mask_t _mask_unused;
#else
#error Unknown cache mask storage type.
#endif
uint16_t _flags;
uint16_t _occupied;
}
其中,uintptr_t
是unsigned long
的别名,占8个字节
;mask_t
是uint_32_t
的别名,占4个字节
。
另外,我们看到,cache_t
内部针对不同平台使用了不同的存储结构,我们可以在objc-runtime-new.h
中看到对于各个存储类型的定义
#define CACHE_MASK_STORAGE_OUTLINED 1
#define CACHE_MASK_STORAGE_HIGH_16 2
#define CACHE_MASK_STORAGE_LOW_4 3
#if defined(__arm64__) && __LP64__
//arm64架构且是64位Unix系统,也就是我们现在的iOS设备
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16
#elif defined(__arm64__) && !__LP64__
//arm64架构但不是64位Unix系统,iphone 5s之前的设备都是32位
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_LOW_4
#else
//其它,MAC_OS设备
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_OUTLINED
#endif
回到我们的cache_t
结构体中,无论是哪种平台,根据内存对齐规则,它需要的存储空间都是16字节
。
我们再看class_data_bits_t
的结构,它内部的结构比较简单
struct class_data_bits_t {
uintptr_t bits;
}
所以,class_data_bits_t bits
占8字节。
综上所述,struct objc_class
所占的内存如下图所示,正好是40个字节
所以,我们得出结论,在内存中,类对象
与元类
的存储是紧密挨在一起的。
二、class_data_bits_t
接下来我们分析class_data_bits_t
,在objc781
源码中,class_data_bits_t
和以前有了很大的改变,现在的源码中,它只有一个成员bits
。
想要获取
class_rw_t
,可以通过struct objc_class
的常成员函数class_rw_t* data()
来获取;或者通过地址偏移
先获得class_data_bits_t
,再通过它的常成员函数class_rw_t* data()
来获取。想要获取
class_ro_t
,可以通过struct class_data_bits_t
的成员方法const class_ro_t *safe_ro()
获取;或者在获取到class_rw_t
的数据后,使用struct class_rw_t
的常成员函数class_ro_t *ro()
来获取。
1.地址偏移
在测试class_data_bits_t
之前,我们先介绍一下什么是地址偏移
。
有如下C语言的代码:
int a[] = {0,1,2,3,4,5,6,7,8,9};
int temp1 = a[4];
int temp2 = *(a+4);
我们知道,C语言中,数组名就是指向数组首地址的指针
,我们在获取数组元素的时候,可以使用下标获取a[4]
,也可以通过指针偏移的方式*(a+4)
,因为a
有被定义为了int
类型,所以,加一个偏移量就是偏移一个int
的字节大小,在上例中,a[4]
和*(a+4)
获取到的值相同,都是获取数组第5个元素的值。
在内存中
,我们要读取内存中的值也可以使用这样的方式,由于地址是没有类型的,所以我们的偏移量需要直接设置为多少字节
。
如我们有了struct objc_class
的指针,根据我们之前对它内部结构的分析,我们获取它指向的内容地址后,可以如下得到各个成员的地址:
-
前面8个字节
就是isa
-
偏移8个字节
可以得到父类指针superclass
-
偏移16个字节
可以得到缓存cache
-
偏移32个字节
可以得到class_data_bits_t
类型的bits
数据
下面我们根据这个思想使用lldb
找到class_data_bits_t
,并且分析它内部的成员。
2.lldb
分析class_data_bits_t
1).设置调试案例
我们打开可编译的源码,在main.m
中添加代码如下:
@interface LWPerson : NSObject{
NSInteger a;
NSString *b;
CGFloat c;
}
//姓名
@property (nonatomic,copy) NSString *name;
//年龄
@property (nonatomic,assign) short age;
//性别
@property (nonatomic,assign) BOOL isMan;
@end
@implementation LWPerson
- (void)sayHello{
NSLog(@"%s",__func__);
}
- (void)sayByeBye{
NSLog(@"%s",__func__);
}
+ (void)eat{
NSLog(@"%s",__func__);
}
+ (void)drink{
NSLog(@"%s",__func__);
}
@end
int main(int argc, const char * argv[]) {
LWPerson *person = [[LWPerson alloc]init];
person.name = @"Jobs";
person.age = 77;
person.isMan = YES;
[person sayHello];
[person sayByeBye];
[LWPerson eat];
[LWPerson drink];
return 0;
}
2).lldb
调试查看class_rw_t
结构
我们在[LWPerson drink];
前添加一个断点进行调试。
//获取person的类LWPerson的地址
(lldb) p/x person.class
(Class) $0 = 0x0000000100002368 LWPerson
//地址偏移32个字节获取class_data_bits_t,16进制下就是加上20
(lldb) p (class_data_bits_t *)0x0000000100002388
(class_data_bits_t *) $1 = 0x0000000100002388
//调用class_data_bits_t的成员方法data()获取到class_rw_t的指针
(lldb) p $1->data()
(class_rw_t *) $2 = 0x0000000100655680
(lldb) p *$2
//查看class_rw_t的内容
(class_rw_t) $3 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic = 4294975656
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
3).class_ro_t
、class_rw_t
关于class_ro_t
、class_rw_t
的探索过程我们以后再去探究,关于这两个结构体,我们先大致解释一下:
-
class_ro_t
是编译期生成的,它存储了当前类在编译期
就已经确定的属性
、方法
以及协议
,它里面是没有分类中定义的方法
和协议
的。 -
class_rw_t
是在运行时生成的,它在realizeClass
中生成,它包含了class_ro_t
。它在_objc_init
方法中关于dyld
的回调的map_images
中最终将分类的方法
与协议
都插入到自己的方法列表
、协议列表
中。它不包含成员变量列表
,因为成员变量列表
是在编译期就确定好的,它只保存在class_ro_t
中。不过,class_rw_t
中包含了一个指向class_ro_t
的指针。
在class_rw_t
的类结构中,有如下一些成员函数:
struct class_rw_t {
public:
//获取class_ro_t
const class_ro_t *ro() const {
auto v = get_ro_or_rwe();
if (slowpath(v.is())) {
return v.get()->ro;
}
return v.get();
}
//设置class_ro_t
void set_ro(const class_ro_t *ro) {
auto v = get_ro_or_rwe();
if (v.is()) {
v.get()->ro = ro;
} else {
set_ro_or_rwe(ro);
}
}
//获取方法列表
const method_array_t methods() const {
auto v = get_ro_or_rwe();
if (v.is()) {
return v.get()->methods;
} else {
return method_array_t{v.get()->baseMethods()};
}
}
//获取属性列表
const property_array_t properties() const {
auto v = get_ro_or_rwe();
if (v.is()) {
return v.get()->properties;
} else {
return property_array_t{v.get()->baseProperties};
}
}
//获取协议列表
const protocol_array_t protocols() const {
auto v = get_ro_or_rwe();
if (v.is()) {
return v.get()->protocols;
} else {
return protocol_array_t{v.get()->baseProtocols};
}
}
};
根据这些暴露出来的方法,我们继续使用lldb
查看class_rw_t
的内容。
4).寻找实例方法
我们继续进行lldb
调试,先看看有哪些方法
//获取方法列表
(lldb) p $3.methods()
(const method_array_t) $4 = {
list_array_tt = {
= {
list = 0x00000001000020f0
arrayAndFlag = 4294975728
}
}
}
//读取实际的方法列表(list)
(lldb) p $4.list
(method_list_t *const) $5 = 0x00000001000020f0
//读取list内部的结构
(lldb) p *$5
(method_list_t) $6 = {
entsize_list_tt = {
entsizeAndFlags = 26
count = 9
first = {
name = "sayHello"
types = 0x0000000100000ebb "v16@0:8"
imp = 0x0000000100000b90 (LWObjc`-[LWPerson sayHello] at main.m:27)
}
}
}
//获取第一个方法,看得出来是- (void)sayHello
(lldb) p $6.get(0)
(method_t) $7 = {
name = "sayHello"
types = 0x0000000100000ebb "v16@0:8"
imp = 0x0000000100000b90 (LWObjc`-[LWPerson sayHello] at main.m:27)
}
//获取第二个方法,看得出来是- (void)sayByeBye
(lldb) p $6.get(1)
(method_t) $8 = {
name = "sayByeBye"
types = 0x0000000100000ebb "v16@0:8"
imp = 0x0000000100000bc0 (LWObjc`-[LWPerson sayByeBye] at main.m:30)
}
////获取第三个方法,看得出来是isMan的getter方法
(lldb) p $6.get(2)
(method_t) $9 = {
name = "isMan"
types = 0x0000000100000efd "c16@0:8"
imp = 0x0000000100000c90 (LWObjc`-[LWPerson isMan] at main.m:21)
}
//获取第四个方法,看得出来是isMan的setter方法
(lldb) p $6.get(3)
(method_t) $10 = {
name = "setIsMan:"
types = 0x0000000100000f05 "v20@0:8c16"
imp = 0x0000000100000cb0 (LWObjc`-[LWPerson setIsMan:] at main.m:21)
}
//获取第五个方法,看得出来是c++析构方法
(lldb) p $6.get(4)
(method_t) $11 = {
name = ".cxx_destruct"
types = 0x0000000100000ebb "v16@0:8"
imp = 0x0000000100000cd0 (LWObjc`-[LWPerson .cxx_destruct] at main.m:25)
}
//获取第六个方法,看得出来是name的getter方法
(lldb) p $6.get(5)
(method_t) $12 = {
name = "name"
types = 0x0000000100000ed7 "@16@0:8"
imp = 0x0000000100000bf0 (LWObjc`-[LWPerson name] at main.m:17)
}
//获取第七个方法,看得出来是name的setter方法
(lldb) p $6.get(6)
(method_t) $13 = {
name = "setName:"
types = 0x0000000100000edf "v24@0:8@16"
imp = 0x0000000100000c20 (LWObjc`-[LWPerson setName:] at main.m:17)
}
//获取第八个方法,看得出来是age的getter方法
(lldb) p $6.get(7)
(method_t) $14 = {
name = "age"
types = 0x0000000100000eea "s16@0:8"
imp = 0x0000000100000c50 (LWObjc`-[LWPerson age] at main.m:19)
}
//获取第九个方法,看得出来是age的setter方法
(lldb) p $6.get(8)
(method_t) $15 = {
name = "setAge:"
types = 0x0000000100000ef2 "v20@0:8s16"
imp = 0x0000000100000c70 (LWObjc`-[LWPerson setAge:] at main.m:19)
}
//获取第十个方法时提示越界
(lldb) p $6.get(9)
Assertion failed: (i < count), function get, file .../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.
从结果我们可以看出,所有的实例方法
和属性的setter
、getter
方法都存放在类对象的class_rw_t
的方法列表中。
方法types
v16@0:8
的解释:
v
表示方法返回值是void
,16
表示方法所有参数一共占16
字节,由于所有OC方法有两个默认的参数id self
和SEL _cmd
,@
表示对象类型,也就是id self
,0
表示这个参数从0
字节位置开始,:
表示方法,也就是SEL _cmd
,8
表示这个参数从第8个字节位置开始
5).探索属性列表
我们再看看属性列表
//获取属性列表
(lldb) p $3.properties()
(const property_array_t) $16 = {
list_array_tt = {
= {
list = 0x0000000100002298
arrayAndFlag = 4294976152
}
}
}
//读取实际的属性列表
(lldb) p $16.list
(property_list_t *const) $17 = 0x0000000100002298
//我们在读取属性列表的时候被打断了,没有权限
(lldb) p *$17
error: Couldn't apply expression side effects : Couldn't dematerialize a result variable: couldn't read its memory
6).探索成员变量列表
在前面的我们提到,成员变量在class_ro_t
中,而不在class_rw_t
中,以为编译结束类的结构就已经确定了,不能够再被改变。
所以,我们先从class_rw_t
获取到class_ro_t
,再查找ivars
//得到`class_ro_t`
(lldb) p $3.ro()
(const class_ro_t *) $19 = 0x00000001000020a8
//查看`class_ro_t`结构
(lldb) p *$19
(const class_ro_t) $20 = {
flags = 388
instanceStart = 8
instanceSize = 48
reserved = 0
ivarLayout = 0x0000000100000e4b "\x11!"
name = 0x0000000100000e42 "LWPerson"
baseMethodList = 0x00000001000020f0
baseProtocols = 0x0000000000000000
ivars = 0x00000001000021d0
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000100002298
_swiftMetadataInitializer_NEVER_USE = {}
}
//获得ivars
(lldb) p $20.ivars
(const ivar_list_t *const) $21 = 0x00000001000021d0
//查看ivars
(lldb) p *$21
(const ivar_list_t) $22 = {
entsize_list_tt = {
entsizeAndFlags = 32
count = 6
first = {
offset = 0x0000000100002310
name = 0x0000000100000e58 "a"
type = 0x0000000100000ec3 "q"
alignment_raw = 3
size = 8
}
}
}
//获取第一个成员变量,这个是a
(lldb) p $22.get(0)
(ivar_t) $23 = {
offset = 0x0000000100002310
name = 0x0000000100000e58 "a"
type = 0x0000000100000ec3 "q"
alignment_raw = 3
size = 8
}
//获取第二个成员变量,这个是b
(lldb) p $22.get(1)
(ivar_t) $24 = {
offset = 0x0000000100002318
name = 0x0000000100000e5a "b"
type = 0x0000000100000ec5 "@\"NSString\""
alignment_raw = 3
size = 8
}
//获取第三个成员变量,这个是c
(lldb) p $22.get(2)
(ivar_t) $25 = {
offset = 0x0000000100002320
name = 0x0000000100000e5c "c"
type = 0x0000000100000ed1 "d"
alignment_raw = 3
size = 8
}
//获取第四个成员变量,这个是_isMan
(lldb) p $22.get(3)
(ivar_t) $26 = {
offset = 0x0000000100002328
name = 0x0000000100000e5e "_isMan"
type = 0x0000000100000ed3 "c"
alignment_raw = 0
size = 1
}
//获取第五个成员变量,这个是_age
(lldb) p $22.get(4)
(ivar_t) $27 = {
offset = 0x0000000100002330
name = 0x0000000100000e65 "_age"
type = 0x0000000100000ed5 "s"
alignment_raw = 1
size = 2
}
//获取第六个成员变量,这个是_name
(lldb) p $22.get(5)
(ivar_t) $28 = {
offset = 0x0000000100002338
name = 0x0000000100000e6a "_name"
type = 0x0000000100000ec5 "@\"NSString\""
alignment_raw = 3
size = 8
}
//获取第七个报错,提示我们越界了
(lldb) p $22.get(6)
Assertion failed: (i < count), function get, file .../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.
3.类方法存储位置的探索
在上面的案例探索中,我们都没有发现类方法+ (void)eat
和+ (void)drink
,这是为什么呢?
在第一部分
中,我们谈了isa指向
的问题,在我们的方法查找的过程中,我们的实例方法
存放在类对象
中,这部分我们已经验证了。而类方法
保存在元类
的方法列表
中。
我们接下来对此进行验证
//得到LWPerson的元类
(lldb) p/x object_getClass(person.class)
(Class) $0 = 0x0000000100002340
(lldb) po $0
LWPerson
//加上32字节,16进制下就是加20,得到class_data_bits_t
(lldb) p (class_data_bits_t *)0x0000000100002360
(class_data_bits_t *) $1 = 0x0000000100002360
//获取class_rw_t
(lldb) p $1->data()
(class_rw_t *) $2 = 0x000000010111a7c0
//查看class_rw_t结构
(lldb) p *$2
(class_rw_t) $3 = {
flags = 2684878849
witness = 1
ro_or_rw_ext = {
std::__1::atomic = 4294975528
}
firstSubclass = nil
nextSiblingClass = 0x00007fff8b25dcd8
}
//获取方法列表
(lldb) p $3.methods()
(const method_array_t) $4 = {
list_array_tt = {
= {
list = 0x0000000100002070
arrayAndFlag = 4294975600
}
}
}
//获取实例的方法列表list
(lldb) p $4.list
(method_list_t *const) $5 = 0x0000000100002070
(lldb) p *$5
(method_list_t) $6 = {
entsize_list_tt = {
entsizeAndFlags = 26
count = 2
first = {
name = "eat"
types = 0x0000000100000ebb "v16@0:8"
imp = 0x0000000100000b30 (KCObjc`+[LWPerson eat] at main.m:33)
}
}
}
//获取第一个方法,我们发现是类方法eat
(lldb) p $6.get(0)
(method_t) $7 = {
name = "eat"
types = 0x0000000100000ebb "v16@0:8"
imp = 0x0000000100000b30 (KCObjc`+[LWPerson eat] at main.m:33)
}
//获取第二个方法,我们发现是类方法drink
(lldb) p $6.get(1)
(method_t) $8 = {
name = "drink"
types = 0x0000000100000ebb "v16@0:8"
imp = 0x0000000100000b60 (KCObjc`+[LWPerson drink] at main.m:36)
}
//获取第三个方法时提示越界了
(lldb) p $6.get(2)
Assertion failed: (i < count), function get, file .../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.
这就是验证了类方法
存放在元类
的方法列表
中。
三、总结
在本篇中,我们研究了isa
的走向以及对class_data_bits_t
的结构进行了分析。
isa
的走向为:对象
->类对象
->元类
->根元类
->根元类
。根元类
继承于NSObject
,它的isa
指向自身。class_data_bits_t
中只有一个bits
,它通过算法可以得到class_rw_t
,class_rw_t
存储了了类的方法列表、属性列表、协议列表;而成员变量
存放在class_ro_t
中。实例方法
存放在类对象
的方法列表
中,类方法
存放在元类
的方法列表
中。
下一篇我们继续分析关于类结构中的cache_t
的作用于内部结构与算法。