一、实例对象、类、元类关系分析
1. 实例对象、类、元类关系图解析
我相信上面这张经典的实例对象、类、元类关系图大家都不陌生,接下来我就来分析下这张图。
新建一个FXPerson类
FXPerson *person = [FXPerson alloc];
复制代码
arm64
和 x86_64
中掩码 ISA_MASK
定义
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
...
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
复制代码
在isa底层结构分析中我们讲过,通过isa & ISA_MSAK
可以查看 isa
指向的类信息。
利用LLDB指令
调试信息
x/4gx: 以16进制形式打印地址内容,读取
4
个16
字节内容
p/x: 打印变量的十六进制格式信息
p 为 expression
的简写 , 并非很多同学理解的 print
哦 ,
po 是 expression -O
( --object-description NSObject
的 description
方法 ) 的简写,打印变量的 description
方法
(lldb) x/4gx person
0x6000000044a0: 0x001d800100003c31 0x0000000000000000
0x6000000044b0: 0x0000000000000000 0x00000000000007fb
(lldb) p/x 0x001d800100003c31 & 0x00007ffffffffff8ULL
(unsigned long long) $9 = 0x0000000100003c30
(lldb) po 0x0000000100003c30
FXPerson
(lldb) x/4gx 0x0000000100003c30
0x100003c30: 0x0000000100003c08 0x00007fff92740118
0x100003c40: 0x0000600002c30f00 0x0004801000000007
(lldb) p/x 0x0000000100003c08 & 0x00007ffffffffff8ULL
(unsigned long long) $11 = 0x0000000100003c08
(lldb) po 0x0000000100003c08
FXPerson
(lldb) x/4gx 0x0000000100003c08
0x100003c08: 0x00007fff927400f0 0x00007fff927400f0
0x100003c18: 0x0000600002c2c880 0x0003e03100000007
(lldb) p/x 0x00007fff927400f0 & 0x00007ffffffffff8ULL
(unsigned long long) $13 = 0x00007fff927400f0
(lldb) po 0x00007fff927400f0
NSObject
(lldb) x/4gx 0x00007fff927400f0
0x7fff927400f0: 0x00007fff927400f0 0x00007fff92740118
0x7fff92740100: 0x0000600003e10700 0x000ae0310000000f
(lldb) p/x 0x00007fff927400f0 & 0x00007ffffffffff8ULL
(unsigned long long) $15 = 0x00007fff927400f0
(lldb) po 0x00007fff927400f0
NSObject
(lldb) x/4gx teacher
0x600000010810: 0x001d800100003be1 0x0000000000000000
0x600000010820: 0x00007fff899ff560 0x0000000000000000
(lldb) p/x 0x001d800100003be1 & 0x00007ffffffffff8ULL
(unsigned long long) $25 = 0x0000000100003be0
(lldb) po 0x0000000100003be0
FXTeacher
(lldb) x/4gx 0x0000000100003be0
0x100003be0: 0x0000000100003bb8 0x0000000100003c30
0x100003bf0: 0x00007fff6afaa140 0x0000801000000000
(lldb) p/x 0x0000000100003bb8 & 0x00007ffffffffff8ULL
(unsigned long long) $27 = 0x0000000100003bb8
(lldb) po 0x0000000100003bb8
FXTeacher
(lldb) x/4gx 0x0000000100003bb8
0x100003bb8: 0x00007fff927400f0 0x0000000100003c08
0x100003bc8: 0x0000600002c04080 0x0003e03100000007
(lldb) p/x 0x00007fff927400f0 & 0x00007ffffffffff8ULL
(unsigned long long) $29 = 0x00007fff927400f0
(lldb) po 0x00007fff927400f0
NSObject
(lldb) x/4gx 0x00007fff927400f0
0x7fff927400f0: 0x00007fff927400f0 0x00007fff92740118
0x7fff92740100: 0x0000600003e10700 0x000ae0310000000f
(lldb) p/x 0x00007fff927400f0 & 0x00007ffffffffff8ULL
(unsigned long long) $15 = 0x00007fff927400f0
(lldb) po 0x00007fff927400f0
NSObject
复制代码
person
实例对象的isa
指向了FXPerson
类FXPerson
类对象的isa
指向了FXPerson
元类FXPerson
元类对象的isa
指向了NSObject
类NSObject
类对象的isa
指向了自己teacher
实例对象的isa
指向了FXTeacher
类FXTeacher
类对象的isa
指向了FXTeacher
元类FXTeacher
元类对象的isa
指向了NSObject
类NSObject
类对象的isa
指向了自己
我们通过LLDB指令调试得到的结论与上图不谋而合,验证完毕!
2.元类的说明
下面来解释什么是 元类
,主要有以下几点说明:
-
我们都知道 对象的
isa
是指向类,类的其实也是一个对象,可以称为类对象,其isa
的位域指向苹果定义的元类
-
元类
是系统给的,其定义和创建都是由编译器完成,在这个过程中,类的归属来自于元类
-
元类
是类对象
的类,每个类都有一个独一无二的元类
用来存储类方法
的相关信息。 -
元类
本身是没有名称的,由于与类相关联,所以使用了同类名一样的名称
二、类的结构定义
我们在isa底层结构分析中讲过生成 cpp
文件的指令。从 cpp
文件中我们可以看到如下这行代码:
typedef struct objc_class *Class;
复制代码
由此可以得出一个结论,Class
是一个 objc_class
的结构体指针,接着,我们需要到 objc源码
探索一下 objc_class
的结构。
typedef struct objc_class *Class;
typedef struct objc_object *id;
复制代码
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();
}
...
}
复制代码
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
...
}
复制代码
从 objc源码
中我们可以看到,我们的类 Class
是 objc_class
类型的, objc_class
类型继承自 objc_object
类型,objc_object
类型有一个 isa
的成员变量。objc_class
结构体是继承自 objc_object
结构体,自然也就有一个 isa
的成员变量,这是爹给的,并且objc_class
结构体的 isa
是指向父类 objc_object
结构体的,这也就说明了类也是一种类对象。
【百度面试题】objc_object 与 对象的关系
所有的 对象
都是以 objc_object
为模板继承过来的
所有的 对象
是 来自 NSObject(OC)
,但是真正到底层的 是一个 objc_object(C/C++)
的结构体类型
【总结】 objc_object
与 对象
的关系 是 继承关系
总结
所有的 对象 + 类 + 元类
都有 isa属性
所有的 对象
都是由 objc_object
继承来的
简单概括就是万物皆 对象
,万物皆来源于 objc_object
,有以下两点结论:
所有以 objc_object
为模板 创建的 对象
,都有 isa属性
所有以 objc_class
为模板,创建的 类
,都有 isa属性
在结构层面可以通俗的理解为 上层OC
与 底层
的对接:
下层
是通过 结构体
定义的 模板
,例如 objc_class、objc_object
上层
是通过 底层
的 模板
创建的 一些类型,例如 FXLPerson
其中 objc_object
和 objc_class
关系图如下:
三、内存偏移
在探究类的属性方法分析之前,先补充一下内存偏移的概念,主要是为了更好理解后面的类的结构体。
int c[4] = {1,2,3}; // 这里先定义一个int数组 c
int *d = c; // 然后定义一个指针d指向 c
NSLog(@"%p - %p - %p",&c,&c[0],&c[1]);
NSLog(@"%p - %p - %p",d,d+1,d+2);
打印结果:0x7ffeefbff4a0 - 0x7ffeefbff4a0 - 0x7ffeefbff4a4 - 0x7ffeefbff4a8
打印结果: 0x7ffeefbff4a0 - 0x7ffeefbff4a4 - 0x7ffeefbff4a8
看这里我们会发现 数组c 的地址 和 c[0] 是同一个地址, 而指针d也是等于 数组c的首地址
并且通过指针d+1,d+2 也能找到数组相应的元素,所以说通过指针偏移可以指向接下来连续的内存地址。
复制代码
四、类的属性和方法分析
1. objc_class分析
上面我们知道了类的结构是什么样的,那么类里面具体都包含了一些什么内容呢,下面我们就来分析一下 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();
}
...
}
复制代码
1.1 第一个属性 Class ISA
被注释掉的,意思就是从父类继承过来的,我们进入 objc_object
里面可以看到,占用8个字节。
struct objc_object {
private:
isa_t isa;
...
}
复制代码
1.2 第二个属性 Class superclass
父类,是一个指针,占用8个字节。
typedef struct objc_class *Class;
复制代码
1.3 第三个属性 cache_t cache
一个结构体,占16个字节。
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets;
explicit_atomic<mask_t> _mask;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
// How much the mask is shifted by.
static constexpr uintptr_t maskShift = 48;
... 都是静态变量,不计入结构体大小
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
// _maskAndBuckets stores the mask shift in the low 4 bits, and
// the buckets pointer in the remainder of the value. The mask
// shift is the value where (0xffff >> shift) produces the correct
// mask. This is equal to 16 - log2(cache_size).
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
static constexpr uintptr_t maskBits = 4;
... 都是静态变量,不计入结构体大小
#else
#error Unknown cache mask storage type.
#endif
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
...都是方法和静态变量,不计入结构体大小
}
复制代码
现在可以看到结构体就只剩下4个成员变量:_buckets、_mask、_flags、_occupied。
①. _buckets:指针类型占8个字节
②. _mask:uint32_t类型,4个字节
#if __LP64__
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
#else
typedef uint16_t mask_t;
#endif
复制代码
typedef unsigned int uint32_t;
复制代码
③. _flags:uint16_t类型,2个字节
typedef unsigned short uint16_t;
复制代码
④. _occupied:uint16_t类型,2个字节
C | OC | 32位 | 64位 |
---|---|---|---|
bool | BOOL(64位) | 1 | 1 |
signed char | (__signed char)in8_t、BOOL(32位) | 1 | 1 |
unsigned char | Boolean | 1 | 1 |
short | int16_t | 2 | 2 |
unsigned short | unichar | 2 | 2 |
int int32_t | NSInterger(32位)、boolean_t(32位) | 4 | 4 |
unsigned int | boolean_t(64位)、NSInterger(32位) | 4 | 4 |
long | NSInterger(64位) | 4 | 8 |
unsigned long | NSInterger(64位) | 4 | 8 |
long long | int64_t | 8 | 8 |
float | CGFloat(32位) | 4 | 4 |
double | CGFloat(64位) | 8 | 8 |
1.4 第四个属性class_data_bits_t bits,是一个结构体,结构体 bits
有一个方法 bits.data()
,我们可以看到方法 data()
是 class_rw_t
类型的,查看 class_rw_t
类型,我们会发现我们要找的属性和方法就在里面
struct class_rw_t {
...
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};
}
}
};
复制代码
1.4.1 class_data_bits_t 结构分析
struct class_data_bits_t {
friend objc_class;
// Values are the FAST_ flags above.
uintptr_t bits;
public:
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
void setData(class_rw_t *newData)
{
ASSERT(!data() || (newData->flags & (RW_REALIZING | RW_FUTURE)));
// Set during realization or construction only. No locking needed.
// Use a store-release fence because there may be concurrent
// readers of data and data's contents.
uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
atomic_thread_fence(memory_order_release);
bits = newBits;
}
}
复制代码
在64位架构CPU下,bits
的第3到第46字节存储 class_rw_t 。class_rw_t
中存储 flags
、witness
、firstSubclass
、nextSiblingClass
以及 class_rw_ext_t。
struct class_rw_ext_t {
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
char *demangledName;
uint32_t version;
};
复制代码
class_rw_ext_t
中存储着 class_ro_t
和 methods (方法列表)
、properties (属性列表)
、protocols (协议列表)
等信息。
而 class_ro_t
中也存储了 baseMethodList (方法列表)
、baseProperties (属性列表)
、baseProtocols (协议列表)
以及 实例变量
、类的名称
、大小
等等信息。
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
};
复制代码
当类被编译的时候,二进制类在磁盘中的表示如下:
首先是类对象本身,包含最常访问的信息:指向元类(isa)
,超类(superclass)
和方法缓存(cache)
的指针,它还具有指向包含更多数据的结构体 class_ro_t
的指针,包含了类的名称,方法,协议,实例变量等等 编译期确定 的信息。其中 ro
表示 read only
的意思。
当类第一次从磁盘加载到内存时,它们总是以这样的形式开始布局的,但是一旦使用它们,就会发生改变:
当类被 Runtime
加载之后,类的结构会发生一些变化,在了解这些变化之前,我们需要知道2个概念:
- Clean Memory:加载后不会发生更改的内存块,
class_ro_t
属于 Clean Memory,因为它是只读的。 - Dirty Memory:运行时会发生更改的内存块,类结构一旦被加载,就会变成 Dirty Memory,因为运行时会向它写入新的数据。例如,我们可以通过
Runtime
给类动态的添加方法。
这里要明确,Dirty Memory 比 Clean Memory 要昂贵得多。因为它需要更多的内存信息,并且只要进程正在运行,就必须保留它。另一方面, Clean Memory 可以进行移除,从而节省更多的内存空间,因为如果你需要 Clean Memory ,系统可以从磁盘中重新加载。
Dirty Memory 是这个类数据 被分成两部分的原因。
对于我们来说,越多的 Clean Memory 显然是更好的,因为它可以 节约更多的内存。我们可以通过分离出永不更改的数据部分,将大多数类数据保留为 Clean Memory,应该怎么做呢?
在介绍优化方法之前,我们先来看一下,在类加载之后,类的结构会变成如何呢?
在类首次被使用的时候,runtime
会为它分配额外的存储容量,用于 读取/写入 数据的一个结构体 class_rw_t
。
在这个结构体中,存储了只有在运行时才会生成的新信息。例如:所有的类都会链接成一个树状结构,这是通过 firstSubclass
和 nextSiblingClass
指针实现的,这允许 runtime
遍历当前使用的所有的类。但是为什么在这里还要有方法列表和属性列表等信息呢? 因为他们在运行时是可以更改的。当 category
被加载的时候,它可以向类中添加新的方法。而且程序员也可以通过runtime API
动态的添加。
class_ro_t
是只读的,存放的是 编译期间就确定 的字段信息;而 class_rw_t
是在 runtime
时才创建的,它会先将 class_ro_t
的内容拷贝一份,再将类的分类的属性、方法、协议等信息添加进去,之所以要这么设计是因为 Objective-C
是动态语言,你可以在运行时更改它们方法,属性等,并且分类可以在不改变类设计的前提下,将新方法添加到类中。
因为 class_ro_t
是只读的,所以我们需要在 class_rw_t
中追踪这些东西。而这样做,显然是会占用相当多的内存的。
事实证明,class_rw_t
会占用比 class_ro_t
占用更多的内存,在 Apple
的测试中,iPhone在系统中大约有 30MB 的 class_rw_t
结构。应该如何优化这些内存呢?
通过测量实际设备上的使用情况,大约只有 10% 的类实际会存在动态的更改行为(如动态添加方法,使用 Category
方法等)。因此,苹果的工程师 把这些动态的部分提取了出来单独放在一个区域,我们称之为 class_rw_ext_t
,这样的设计使得 class_rw_t
的大小减少了一半, 所以,结构会变成这个样子。
大约90%的类从来不需要这些扩展数据,经过拆分,可以把 90% 的类优化为 Clean Memory,在系统层面,苹果测试了取得的效果是,大约节省了 14MB 的内存,使内存可用于更有效的用途。
2. 类属性方法分析
下面我们就针对下面的代码进行LLDB调试
@interface FXPerson : NSObject
@property (nonatomic,copy) NSString *name; // XuPengfei
@property (nonatomic,copy) NSString *nickName; // FX
@property (nonatomic) int height; // 180
- (void)sayHello;
+ (void)sayBye;
@end
复制代码
FXPerson *person = [[FXPerson alloc] init];
person.name = @"XuPengfei";
person.nickName = @"FX";
person.height = 180;
复制代码
2.1 打印FXPerson类信息
(lldb) x/4gx FXPerson.class
0x1000022b0: 0x0000000100002288 0x0000000100334140
0x1000022c0: 0x00000001038be0d0 0x0001802c00000007
复制代码
2.2 在1. objc_class分析里面已经分析过类信息结构为Class ISA、Class superclass、cache_t cache、class_data_bits_t bits;并且Class ISA、Class superclass、cache_t cache的大小分别为8、8、16字节,所以类信息的首地址 8 + 8 + 16 = 32
个字节就可以得到 class_data_bits_t
类型的 bits
信息。(0x1000022b0 + 0x20(32bit) = 0x1000022d0
)
(lldb) p (class_data_bits_t *)0x1000022d0
(class_data_bits_t *) $1 = 0x00000001000022d0
复制代码
2.3 接下来我们打印 bits.data()
信息。
(lldb) p $1->data()
(class_rw_t *) $2 = 0x00000001038bdd00
(lldb) p *$2
(class_rw_t) $3 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = 4294975648
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
复制代码
2.4 在1.4 class_data_bits_t bits 中我们分析过 bits.data()
是 class_rw_t
类型, class_rw_t
类型有methods()、properties()、protocols()
等成员变量,我们可以打印 properties()
信息
(lldb) p $3.properties()
(const property_array_t) $4 = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x0000000100002218
arrayAndFlag = 4294976024
}
}
}
(lldb) p $4.list
(property_list_t *const) $5 = 0x0000000100002218
(lldb) p *$5
(property_list_t) $6 = {
entsize_list_tt<property_t, property_list_t, 0> = {
entsizeAndFlags = 16
count = 3
first = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
}
}
(lldb) p $6.get(0)
(property_t) $7 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
(lldb) p $6.get(1)
(property_t) $8 = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
(lldb) p $6.get(2)
(property_t) $9 = (name = "height", attributes = "Ti,N,V_height")
(lldb) p $6.get(3) // 获取第4个属性,报错:数组越界
Assertion failed: (i < count), function get, file /Users/xxx/xxx, line 438.
复制代码
2.5 接下来我们查看 methods()
信息
(lldb) p $3.methods()
(const method_array_t) $10 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x00000001000020e8
arrayAndFlag = 4294975720
}
}
}
(lldb) p $10.list
(method_list_t *const) $11 = 0x00000001000020e8
(lldb) p *$11
(method_list_t) $12 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 8
first = {
name = "sayHello"
types = 0x0000000100000f6a "v16@0:8"
imp = 0x0000000100000cb0 (KCObjc`-[FXPerson sayHello])
}
}
}
(lldb) p $12.get(0)
(method_t) $13 = {
name = "sayHello"
types = 0x0000000100000f6a "v16@0:8"
imp = 0x0000000100000cb0 (KCObjc`-[FXPerson sayHello])
}
(lldb) p $12.get(1)
(method_t) $14 = {
name = ".cxx_destruct"
types = 0x0000000100000f6a "v16@0:8"
imp = 0x0000000100000dc0 (KCObjc`-[FXPerson .cxx_destruct])
}
(lldb) p $12.get(2)
(method_t) $15 = {
name = "name"
types = 0x0000000100000f80 "@16@0:8"
imp = 0x0000000100000cc0 (KCObjc`-[FXPerson name])
}
(lldb) p $12.get(3)
(method_t) $16 = {
name = "height"
types = 0x0000000100000f93 "i16@0:8"
imp = 0x0000000100000d80 (KCObjc`-[FXPerson height])
}
(lldb) p $12.get(4)
(method_t) $17 = {
name = "setName:"
types = 0x0000000100000f88 "v24@0:8@16"
imp = 0x0000000100000cf0 (KCObjc`-[FXPerson setName:])
}
(lldb) p $12.get(5)
(method_t) $18 = {
name = "setHeight:"
types = 0x0000000100000f9b "v20@0:8i16"
imp = 0x0000000100000da0 (KCObjc`-[FXPerson setHeight:])
}
(lldb) p $12.get(6)
(method_t) $19 = {
name = "setNickName:"
types = 0x0000000100000f88 "v24@0:8@16"
imp = 0x0000000100000d50 (KCObjc`-[FXPerson setNickName:])
}
(lldb) p $12.get(7)
(method_t) $20 = {
name = "nickName"
types = 0x0000000100000f80 "@16@0:8"
imp = 0x0000000100000d20 (KCObjc`-[FXPerson nickName])
}
(lldb) p $12.get(8) // 报错:数组越界
Assertion failed: (i < count), function get, file /Users/xxx/xxx
复制代码
2.6 问题来了,我们不是还有 类方法
没有打印吗?为什么会报数组越界的错误呢?很简单,前面我们已经说过,每个 类
都有一个独一无二的 元类
,用来存储 类方法
。我们在一、实例对象、类、元类关系分析已经分析过,类对象
的 isa指针
指向 类对象
的 元类
。所以 元类
的首地址 8 + 8 + 16 = 32
个字节就可以得到 元类
的 class_data_bits_t
类型的 bits
信息。
(lldb) p (class_data_bits_t *)0x00000001000022a8
(class_data_bits_t *) $21 = 0x00000001000022a8
复制代码
2.7 接下来我们打印 元类
的 bits.data()
信息。
lldb) p $21->data()
(class_rw_t *) $22 = 0x00000001038bdce0
(lldb) p *$22
(class_rw_t) $23 = {
flags = 2684878849
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = 4294975544
}
firstSubclass = nil
nextSiblingClass = 0x00007fff88e04cd8
}
复制代码
2.8 接下来我们查看 元类
的 methods()
信息
(const method_array_t) $24 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100002080
arrayAndFlag = 4294975616
}
}
}
(lldb) p $24.list
(method_list_t *const) $25 = 0x0000000100002080
(lldb) p *$25
(method_list_t) $26 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 1
first = {
name = "sayBye"
types = 0x0000000100000f6a "v16@0:8"
imp = 0x0000000100000ca0 (KCObjc`+[FXPerson sayBye])
}
}
}
(lldb) p $26.get(0)
(method_t) $27 = {
name = "sayBye"
types = 0x0000000100000f6a "v16@0:8"
imp = 0x0000000100000ca0 (KCObjc`+[FXPerson sayBye])
}
(lldb) p $26.get(1) // 数组越界,因为我们只有1个类方法,合情合理
Assertion failed: (i < count), function get, file /Users/xxx/xxx
复制代码