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流程图.png全面总结
实例对象
的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)
从上面的分析得知:类
方法是保存在类对象的元类
中的。
网友评论