前言:
1.我们前面已经对isa的结构进行了分析,但整个isa指向过程是什么呢?
2.我们经常看到的那张经典的isa走位图又该如何理解呢?
3.前面我们已经知道实例对象的isa指针指向了存储类信息的类对象,那么这个存储类信息的类的结构又是怎样的呢?
使用LLDB探索isa走位
-
首先定义LGHPerson,LGHTeacher类,并创建实例对象,先在main函数里面的objc打个断点。
然后使用lldb, 查看p对象的内存
(lldb) po p
(lldb) x/4gx 0x1006d1dc0
0x1006d1dc0: 0x001d800100002181 0x0000000000000000
0x1006d1dd0: 0x726573554b575b2d 0x7320747069726353
- 获取isa的shiftcls的地址
(lldb) p/x 0x001d800100002181 & 0x00007ffffffffff8ULL
(unsigned long long) $3 = 0x0000000100002180
- 读取该段地址,与
x LGHPerson.class
比较,发现一致,说明p.isa --> 存储类信息的类。
(lldb) x 0x0000000100002180
0x100002180: 58 21 00 00 01 00 00 00 40 11 3f 00 01 00 00 00 X!......@.?.....
0x100002190: 30 57 6d 00 01 00 00 00 07 00 00 00 10 80 04 00 0Wm.............
(lldb) x LGHPerson.class
0x100002180: 58 21 00 00 01 00 00 00 40 11 3f 00 01 00 00 00 X!......@.?.....
0x100002190: 30 57 6d 00 01 00 00 00 07 00 00 00 10 80 04 00 0Wm.............
(lldb) x object_getClass(p)
0x100002180: 58 21 00 00 01 00 00 00 40 11 3f 00 01 00 00 00 X!......@.?.....
0x100002190: 30 57 6d 00 01 00 00 00 07 00 00 00 10 80 04 00 0Wm.............
- 接下来我们x/4gx 打印存储类信息的类对象的内存
(lldb) x/4gx 0x0000000100002180
0x100002180: 0x0000000100002158 0x00000001003f1140
0x100002190: 0x00000001006d5730 0x0004801000000007
- 接下来,我们看看类对象得isa的shiftcls
(lldb) p/x 0x0000000100002158 & 0x00007ffffffffff8ULL
(unsigned long long) $8 = 0x0000000100002158
- 读取类对象得isa的shiftcls存储的内存,其实就是元类,系统会自动帮我生成。所以类对象的isa-->元类
(lldb) x/4gx 0x0000000100002158
0x100002158: 0x00000001003f10f0 0x00000001003f10f0
0x100002168: 0x0000000101208fc0 0x0001e03100000007
- 我们继续那元类的isa,获取shiftcls
(lldb) p/x 0x00000001003f10f0 & 0x00007ffffffffff8ULL
(unsigned long long) $13 = 0x00000001003f10f0
- 读取地址 ,并与存储NSObject类信息的类对象地址对比,发现一致,说明元类的isa-->存储NSObject类信息的类(根元类)
(lldb) x/4gx 0x00000001003f10f0
0x1003f10f0: 0x00000001003f10f0 0x00000001003f1140
0x1003f1100: 0x000000010070a2f0 0x0005e03100000007
(lldb) x/4gx NSObject.class
0x1003f1140: 0x00000001003f10f0 0x0000000000000000
0x1003f1150: 0x000000010110b1c0 0x0001801000000003
- 我们继续探索根元类的isa指向,发现指向根元类自己
(lldb) p/x 0x00000001003f10f0 & 0x00007ffffffffff8ULL
(unsigned long long) $16 = 0x00000001003f10f0
(lldb) x/4gx 0x00000001003f10f0
0x1003f10f0: 0x00000001003f10f0 0x00000001003f1140
0x1003f1100: 0x000000010070a2f0 0x0005e03100000007
总结:
1.实例对象的isa -->类对象
2.类对象的isa指向元类对象
3.元类对象的isa指向根元类对象
4.根元类对象的isa指向自身
同理LGHTeacher的走位图
存储类信息的类结构
我们看到objc_class定义,但我们发现OBJC2_UNAVAILABLE,这是已经废弃掉的结构。
// 旧的类结构:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
我们继续寻找objc_class的新结构,我们可以看到objc_class
的新版定义(objc4-781版本)如下,有以下几个属性
// 在最新的objc源码里面:
struct objc_class : objc_object {
// Class ISA; //8字节
Class superclass; //Class 类型 8字节
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
//....方法部分省略,未贴出
}
isa
属性:继承自objc_object的isa,占 8字节superclas
属性:Class类型
,Class
是由objc_object
定义的,是一个指针,占8字节cache
属性:简单从类型class_data_bits_t目前无法得知,而class_data_bits_t是一个结构体类型,结构体的内存大小需要根据内部的属性来确定,而结构体指针才是8字节bits
属性:只有首地址经过上面3个属性的内存大小总和的平移,才能获取到bits
计算 cache_t
结构体的内存大小
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic _buckets; // 是一个结构体指针类型,占8字节
explicit_atomic _mask; //是mask_t 类型,而 mask_t 是 unsigned int 的别名,占4字节
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic _maskAndBuckets; //是指针,占8字节
mask_t _mask_unused; //是mask_t 类型,而 mask_t 是 uint32_t 类型定义的别名,占4字节
#if __LP64__
uint16_t _flags; //是uint16_t类型,uint16_t是 unsigned short 的别名,占 2个字节
#endif
uint16_t _occupied; //是uint16_t类型,uint16_t是 unsigned short 的别名,占 2个字节
-
计算前两个属性的内存大小,有以下两种情况,最后的内存大小总和都是12字节
- 【情况一】if流程
-
buckets
类型是struct bucket_t *
,是结构体指针类型,占8字节
-
mask
是mask_t 类型
,而 mask_t 是unsigned int
的别名,占4字节
-
- 【情况二】elseif流程
- 【情况一】if流程
_maskAndBuckets 是uintptr_t类型,它是一个指针,占
8字节
_mask_unused 是mask_t 类型,而 mask_t 是 uint32_t 类型定义的别名,占
4字节
_flags 是uint16_t类型,uint16_t是 unsigned short 的别名,占
2个字节
_occupied 是uint16_t类型,uint16_t是 unsigned short 的别名,占
2个字节
总结:所以最后计算出cache类的内存大小 = 12 + 2 + 2 = 16字节
获取bits
所以有上述计算可知,想要获取bits
的中的内容,只需通过类的首地址平移32字节
即可。如下:($1+32)
(lldb) p/x LGHPerson.class
(Class) $0 = 0x00000001000022a0 LGHPerson
(lldb) p/x 0x00000001000022a0
(long) $1 = 0x00000001000022a0
(lldb) p/x (class_data_bits_t *)($1+32)
(class_data_bits_t *) $2 = 0x00000001000022c0
接下来获取properties()
,得到里面存储了name属性
(lldb) p $4.properties()
(const property_array_t) $12 = {
list_array_tt = {
= {
list = 0x00000001000021a8
arrayAndFlag = 4294975912
}
}
}
(lldb) p $12.list
(property_list_t *const) $13 = 0x00000001000021a8
(lldb) p *$13
(property_list_t) $14 = {
entsize_list_tt = {
entsizeAndFlags = 16
count = 1
first = (name = "name", attributes = "T@\"NSString\",&,N,V_name")
}
}
(lldb) p $14.get(0)
(property_t) $15 = (name = "name", attributes = "T@\"NSString\",&,N,V_name")
数组越界,说明只存储了name
(lldb) p $14.get(1)
Assertion failed: (i < count), function get, file /Users/xiaofeiguan/Desktop/04-isa指向&类初探/03-自己补充/可编译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.
类信息里面的properties()里面只存储属性,没有存储成员变量
接下来获取methods()
,得到-sayHello -setName -name 这个实例方法
(lldb) p $4.methods()
(const method_array_t) $5 = {
list_array_tt = {
= {
list = 0x00000001000020d8
arrayAndFlag = 4294975704
}
}
}
(lldb) p $5.list
(method_list_t *const) $6 = 0x00000001000020d8
(lldb) p *$6
(method_list_t) $7 = {
entsize_list_tt = {
entsizeAndFlags = 26
count = 4
first = {
name = "sayHello"
types = 0x0000000100000f42 "v16@0:8"
imp = 0x0000000100000d70 (KCObjc`-[LGHPerson sayHello] at main.m:21)
}
}
}
(lldb) p $7.get(0)
(method_t) $8 = {
name = "sayHello"
types = 0x0000000100000f42 "v16@0:8"
imp = 0x0000000100000d70 (KCObjc`-[LGHPerson sayHello] at main.m:21)
}
(lldb) p $7.get(1)
(method_t) $9 = {
name = ".cxx_destruct"
types = 0x0000000100000f42 "v16@0:8"
imp = 0x0000000100000d80 (KCObjc`-[LGHPerson .cxx_destruct] at main.m:20)
}
(lldb) p $7.get(2)
(method_t) $10 = {
name = "name"
types = 0x0000000100000f58 "@16@0:8"
imp = 0x0000000100000dc0 (KCObjc`-[LGHPerson name] at main.m:15)
}
(lldb) p $7.get(3)
(method_t) $11 = {
name = "setName:"
types = 0x0000000100000f60 "v24@0:8@16"
imp = 0x0000000100000de0 (KCObjc`-[LGHPerson setName:] at main.m:15)
}
数组越界,说明类信息
里面只存储实例方法
,不存储类方法
。
(lldb) p $7.get(4)
Assertion failed: (i < count), function get, file /Users/xiaofeiguan/Desktop/04-isa指向&类初探/03-自己补充/可编译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.