ISA指向、类结构
1.ISA指向
上次在对象本质和ISA指针学习中分析了对象的ISA指针指向当前的类对象,那么类对象的ISA指针指向什么,OC中整体的ISA指针指向是什么样的呢?这篇文章来学习下。
创建LGPerson类,继承于NSObject。
NSObject *objc1 = [NSObject alloc];
LGPerson *objc2 = [LGPerson alloc];
// 获取metalClass
Class a = objc_getMetaClass("LGPerson");
- 开始调试
(lldb) po objc1
<NSObject: 0x1006cc3e0>
(lldb) x/4gx 0x1006cc3e0
0x1006cc3e0: 0x001d80010034c141 0x0000000000000000
0x1006cc3f0: 0x726573554b575b2d 0x43746e65746e6f43
(lldb) p 0x001d80010034c141 & 0x00007ffffffffff8ULL
(unsigned long long) $1 = 4298424640
(lldb) po $1
NSObject
对象objc1的isa指针0x001d80010034c141,经过计算得到指向了NSObject类,为Class类型。
// Class 声明
typedef struct objc_class *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
.....
}
// objc_class 父类
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
...
}
objc_class中的 ISA是从objc_object中继承过来的,接下来打印$1内部储存结构继续调试。
(lldb) x $1
0x10034c140: f0 c0 34 00 01 00 00 00 00 00 00 00 00 00 00 00 ..4.............
0x10034c150: 30 0f 6d 00 01 00 00 00 07 00 00 00 10 80 04 00 0.m.............
(lldb) x/4gx 0x10034c140
0x10034c140: 0x000000010034c0f0 0x0000000000000000
0x10034c150: 0x00000001006d0f30 0x0004801000000007
(lldb) po 0x000000010034c0f0
NSObject
0x000000010034c0f0是8字节的Class,即为NSObject,0x0000000000000000 通过objc_clss结构分析,应为superclass,即为nil。
疑问 0x000000010034c0f0 为NSObject的ISA,为什么打印出来还是NSObject?
(lldb) x/4gx 0x000000010034c0f0
0x10034c0f0: 0x000000010034c0f0 0x000000010034c140
0x10034c100: 0x00000001006d1870 0x0004e03100000007
(lldb) po 0x000000010034c0f0
NSObject
(lldb) po 0x000000010034c140
NSObject
0x000000010034c0f0的ISA又只向了自己,父类是0x000000010034c140 即是NSObject。问题来了,0x000000010034c0f0与0x000000010034c140到底哪个是我们经常用的NSObject。
(lldb) p NSObject.class
(Class) $9 = NSObject
(lldb) x $9
0x10034c140: f0 c0 34 00 01 00 00 00 00 00 00 00 00 00 00 00 ..4.............
0x10034c150: 30 0f 6d 00 01 00 00 00 07 00 00 00 10 80 04 00 0.m.............
(lldb) po 0x10034c140
NSObject
(lldb) p objc_getMetaClass("NSObject")
(Class) $11 = 0x000000010034c0f0
初步总结
objc1为NSObject对象,objc1中的isa指针指向NSObject类,NSObject类中的isa指针指向NSObject元类,NSObject元类中的isa指针指向自己。NSObject元类继承于NSObject。

再次分析
(lldb) po objc2
<LGPerson: 0x1006cc130>
(lldb) p 0x001d8001000080f9 & 0x00007ffffffffff8ULL
(unsigned long long) $17 = 4295000312
(lldb) po $17
LGPerson
(lldb) x/4gx $17
0x1000080f8: 0x00000001000080d0 0x000000010034c140
0x100008108: 0x0000000100723a90 0x0004801000000007
(lldb) po 0x00000001000080d0
LGPerson
(lldb) po 0x000000010034c140
NSObject
(lldb) x/4gx 0x00000001000080d0
0x1000080d0: 0x000000010034c0f0 0x000000010034c0f0
0x1000080e0: 0x00000001006d19f0 0x0003e03100000007
(lldb) po 0x000000010034c0f0
NSObject
(lldb) p objc_getMetaClass("LGPerson")
(Class) $21 = 0x00000001000080d0
objc2对象中isa指向地址为0x1000080f8 的LGPerson类,LGPerson类中的isa指针指向地址为0x00000001000080d0的LGPerson元类,LGPerson元类的isa指针指向地址为0x000000010034c0f0的NSObject元类。后面的和我们前面验证的就一样了。

苹果提供的走位图

全面总结
实例对象的isa指针指向它的类对象,类对象的isa指针指向它的元类,元类的isa指针指向根元类,根元类的isa指针指向自己。
子类的元类继承自父类的元类,直到根元类,根元类继承自根类即NSObject。
2.类结构分析
内存偏移
int p1 = 10;
int p2 = 10;
NSLog(@"%d -- %p", p1, &p1);
NSLog(@"%d -- %p", p2, &p2);
int c[4] = {1,2,3,4};
int *d = c;
NSLog(@"%p -- %p - %p", &c, &c[0], &c[1]);
NSLog(@"%p -- %p - %p", d, d+1, d+2);
打印结果
2020-10-23 18:15:05.761402+0800 KCObjc[21732:317675] 10 -- 0x7ffeefbff498
2020-10-23 18:15:05.762237+0800 KCObjc[21732:317675] 10 -- 0x7ffeefbff49c
2020-10-23 18:15:05.762297+0800 KCObjc[21732:317675] 0x7ffeefbff4a0 -- 0x7ffeefbff4a0 - 0x7ffeefbff4a4
2020-10-23 18:15:05.762352+0800 KCObjc[21732:317675] 0x7ffeefbff4a0 -- 0x7ffeefbff4a4 - 0x7ffeefbff4a8
0x7ffeefbff498与0x7ffeefbff49c为p1、p2的指针地址,两个地址相差4字节,正好为 int类型占用的大小。
通过数组打印可知,数组c的首地址和第一个元素的地址相同,第一个元素和第二个元素地址相差4字节,即为int --(储存的类型)类型所占用的大小,也可用d指针 +1、+2操作指向不同元素。
所以我们用内存偏移的方式获取类中的储存信息。
首先看下类结构的声明:参考objc-781源码
struct objc_class : objc_object {
// Class ISA; //8
Class superclass; //8
cache_t cache; // formerly cache pointer and vtable // 8+4+2+2 = 16
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() const {
return bits.data();
}
......
}
经过寻找并没有找到相关的储存的方法列表、属性列表的地方,但是发现class_rw_t *data()结构体中包含三个方法:
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
.....
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 *>()->methods;
} else {
return method_array_t{v.get<const class_ro_t *>()->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 *>()->properties;
} else {
return property_array_t{v.get<const class_ro_t *>()->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 *>()->protocols;
} else {
return protocol_array_t{v.get<const class_ro_t *>()->baseProtocols};
}
}
}
class_rw_t类型为bits.data()返回值,只要找到bits属性的首地址就可以进行调用了,根据内存偏移原则,只要找到对象的首地址+ISA(父类继承)占用大小+superclass属性占用大小+cache属性占用大小,Class类型我们知道是占用8字节,只差cache属性了。
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets; // 结构体指针类型为 8字节
explicit_atomic<mask_t> _mask; // uint32_t 类型为 4字节
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets; // typedef unsigned long uintptr_t; 8字节
mask_t _mask_unused; // uint32_t 类型为4字节
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
explicit_atomic<uintptr_t> _maskAndBuckets; // typedef unsigned long uintptr_t; 8字节
mask_t _mask_unused; // uint32_t 类型为4字节
/// 不在结构体中储存
static constexpr uintptr_t maskShift = 48;
....
static constexpr uintptr_t maskBits = 4;
static constexpr uintptr_t maskMask = (1 << maskBits) - 1;
static constexpr uintptr_t bucketsMask = ~maskMask;
#else
#error Unknown cache mask storage type.
#endif
#if __LP64__
uint16_t _flags; // short 2字节
#endif
uint16_t _occupied; // short 2字节
.....
}
经过分析#if,#elif条件中属性都为12字节,static声明的属性不会存在结构体中,剩下的 _flags和_occupied属性都占用2字节,所以cache的占用大小为12+2+2为16,总偏移量为8+8+16为32,接下来我们来验证,定义一个LGPerson类。
@interface LGPerson : NSObject
{
NSInteger _age;
}
@property(nonatomic,copy) NSString *name;
-(void)run;
-(void)sing;
+(void)instance;
@end
寻找方法列表
(lldb) p LGPerson.class
(Class) $0 = LGPerson
(lldb) x/4gx $0 // 分4段16进制格式查看LGPerson内存分部
0x100008218: 0x00000001000081f0 0x000000010034c140
0x100008228: 0x0000000100346460 0x0000802400000000
(lldb) p 0x100008238
(long) $1 = 4295000632
(lldb) p (class_data_bits_t *)$1
(class_data_bits_t *) $2 = 0x0000000100008238
(lldb) p $2 ->data() // 调用 data()方法
(class_rw_t *) $3 = 0x0000000100659ee0
(lldb) p $3 ->methods() // 调用methods()方法
(const method_array_t) $4 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x00000001000080f0
arrayAndFlag = 4295000304
}
}
}
(lldb) p $4.list //获取 list属性
(method_list_t *const) $5 = 0x00000001000080f0
(lldb) p *$5 // 获取 method_list_t对象内存
(method_list_t) $6 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 5
first = {
name = "sing"
types = 0x0000000100003f7a "v16@0:8"
imp = 0x0000000100003db0 (KCObjc`-[LGPerson sing])
}
}
}
(lldb) p $6.get(0) // 根据下标获取对象
(method_t) $7 = {
name = "sing"
types = 0x0000000100003f7a "v16@0:8"
imp = 0x0000000100003db0 (KCObjc`-[LGPerson sing])
}
(lldb) p $6.get(1)
(method_t) $8 = {
name = ".cxx_destruct"
types = 0x0000000100003f7a "v16@0:8"
imp = 0x0000000100003e20 (KCObjc`-[LGPerson .cxx_destruct])
}
(lldb) p $6.get(2)
(method_t) $9 = {
name = "name"
types = 0x0000000100003f90 "@16@0:8"
imp = 0x0000000100003dc0 (KCObjc`-[LGPerson name])
}
(lldb) p $6.get(3)
(method_t) $10 = {
name = "setName:"
types = 0x0000000100003f98 "v24@0:8@16"
imp = 0x0000000100003df0 (KCObjc`-[LGPerson setName:])
}
(lldb) p $6.get(4)
(method_t) $11 = {
name = "run"
types = 0x0000000100003f7a "v16@0:8"
imp = 0x0000000100003da0 (KCObjc`-[LGPerson run])
}
(lldb) p $6.get(5) // 越界 数组中有5个元素
Assertion failed: (i < count), function get, file /runtime/objc-runtime-new.h, line 439.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
从上面的分析得知:类对象的实例方法储存在 objc_class结构体-->class_rw_t类型bits.data()方法--->method_array_t类型 methods()-->method_list_t(数组)类型list属性中,问题是并没有找到类方法,问题保留一会分析。
寻找属性列表
(lldb) p $3 ->properties() // $3 为class_rw_t类型
(const property_array_t) $21 = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x00000001000081b8
arrayAndFlag = 4295000504
}
}
}
(lldb) p $21.list // 获取list属性
(property_list_t *const) $22 = 0x00000001000081b8
(lldb) p *$22 // 打印list内存地址
(property_list_t) $23 = {
entsize_list_tt<property_t, property_list_t, 0> = {
entsizeAndFlags = 16
count = 1
first = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
}
}
(lldb) p $23.get(0) // 获取数组元素
(property_t) $24 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
(lldb) p $23.get(1) // 越界 数组中只有一个元素
Assertion failed: (i < count), function get, file /runtime/objc-runtime-new.h, line 439.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
(lldb)
从上面的分析得知:对象的属性储存在 objc_class结构体-->class_rw_t类型bits.data()方法--->property_array_t类型 properties()-->property_list_t(数组)类型list属性中,只找到name成员,没有找到_age成员变量。
寻找成员变量
(lldb) p $3 ->ro()
(const class_ro_t *) $25 = 0x00000001000080a8
(lldb) p *$25
(const class_ro_t) $26 = {
flags = 388
instanceStart = 8
instanceSize = 24
reserved = 0
ivarLayout = 0x0000000100003f3f "\x11"
name = 0x0000000100003f36 "LGPerson"
baseMethodList = 0x00000001000080f0
baseProtocols = 0x0000000000000000
ivars = 0x0000000100008170
weakIvarLayout = 0x0000000000000000
baseProperties = 0x00000001000081b8
_swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $26.ivars
(const ivar_list_t *const) $27 = 0x0000000100008170
(lldb) p *$27
(const ivar_list_t) $28 = {
entsize_list_tt<ivar_t, ivar_list_t, 0> = {
entsizeAndFlags = 32
count = 2
first = {
offset = 0x00000001000081e0
name = 0x0000000100003f4a "_age"
type = 0x0000000100003f82 "q"
alignment_raw = 3
size = 8
}
}
}
(lldb) p $28.get(0)
(ivar_t) $29 = {
offset = 0x00000001000081e0
name = 0x0000000100003f4a "_age"
type = 0x0000000100003f82 "q"
alignment_raw = 3
size = 8
}
(lldb) p $28.get(1)
(ivar_t) $30 = {
offset = 0x00000001000081e8
name = 0x0000000100003f4f "_name"
type = 0x0000000100003f84 "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $28.get(2)
Assertion failed: (i < count), function get, file /runtime/objc-runtime-new.h, line 439.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
从上面的分析得知:类对象的成员变量储存在 objc_class结构体-->class_rw_t类型bits.data()方法--->class_ro_t类型 ro()-->ivar_list_t(数组)类型ivars属性中,包含_name和_age成员变量。
寻找类方法
(lldb) x/4gx LGPerson.class
0x100008218: 0x00000001000081f0 0x000000010034c140
0x100008228: 0x0000000100346460 0x0000802400000000
(lldb) po 0x00000001000081f0 // 获取到元类
LGPerson
(lldb) p 0x00000001000081f0
(long) $2 = 4295000560
(lldb) x/4gx 0x00000001000081f0
0x1000081f0: 0x000000010034c0f0 0x000000010034c0f0
0x100008200: 0x00000001020168f0 0x0001e03500000007
(lldb) p (class_data_bits_t *)0x100008210 // 0x1000081f0 向后移动32字节。
(class_data_bits_t *) $3 = 0x0000000100008210
(lldb) p $3 ->data()
(class_rw_t *) $4 = 0x000000010064d0c0
(lldb) p $4 ->methods()
(const method_array_t) $5 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100008088
arrayAndFlag = 4295000200
}
}
}
(lldb) p $5.list
(method_list_t *const) $6 = 0x0000000100008088
(lldb) p *$6
(method_list_t) $7 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 1
first = {
name = "instance"
types = 0x0000000100003f7a "v16@0:8"
imp = 0x0000000100003d90 (KCObjc`+[LGPerson instance])
}
}
}
(lldb) p $7.get(0)
(method_t) $8 = {
name = "instance"
types = 0x0000000100003f7a "v16@0:8"
imp = 0x0000000100003d90 (KCObjc`+[LGPerson instance])
}
(lldb) p $7.get(1) //越界
Assertion failed: (i < count), function get, file /runtime/objc-runtime-new.h, line 439.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
(lldb)
从上面的分析得知:类方法是保存在类对象的元类中的。
