iOS类的结构分析

实例对象、类、元类关系图

一、实例对象、类、元类关系分析

1. 实例对象、类、元类关系图解析

我相信上面这张经典的实例对象、类、元类关系图大家都不陌生,接下来我就来分析下这张图。
新建一个FXPerson类

FXPerson *person   = [FXPerson alloc];
复制代码

arm64x86_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进制形式打印地址内容,读取 416 字节内容

p/x: 打印变量的十六进制格式信息
pexpression 的简写 , 并非很多同学理解的 print 哦 ,
poexpression -O ( --object-description NSObjectdescription 方法 ) 的简写,打印变量的 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源码 中我们可以看到,我们的类 Classobjc_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_objectobjc_class 关系图如下:
objc_object和objc_class关系图.png

三、内存偏移

在探究类的属性方法分析之前,先补充一下内存偏移的概念,主要是为了更好理解后面的类的结构体。

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_tclass_rw_t 中存储 flagswitnessfirstSubclassnextSiblingClass 以及 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_tmethods (方法列表)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;
};
复制代码

当类被编译的时候,二进制类在磁盘中的表示如下:

编译过程.png

首先是类对象本身,包含最常访问的信息:指向元类(isa)超类(superclass)方法缓存(cache)的指针,它还具有指向包含更多数据的结构体 class_ro_t 的指针,包含了类的名称,方法,协议,实例变量等等 编译期确定 的信息。其中 ro 表示 read only 的意思。

当类第一次从磁盘加载到内存时,它们总是以这样的形式开始布局的,但是一旦使用它们,就会发生改变:

当类被 Runtime 加载之后,类的结构会发生一些变化,在了解这些变化之前,我们需要知道2个概念:

  • Clean Memory:加载后不会发生更改的内存块,class_ro_t 属于 Clean Memory,因为它是只读的。
  • Dirty Memory:运行时会发生更改的内存块,类结构一旦被加载,就会变成 Dirty Memory,因为运行时会向它写入新的数据。例如,我们可以通过 Runtime 给类动态的添加方法。

这里要明确,Dirty MemoryClean Memory 要昂贵得多。因为它需要更多的内存信息,并且只要进程正在运行,就必须保留它。另一方面, Clean Memory 可以进行移除,从而节省更多的内存空间,因为如果你需要 Clean Memory ,系统可以从磁盘中重新加载。

Dirty Memory 是这个类数据 被分成两部分的原因。
对于我们来说,越多的 Clean Memory 显然是更好的,因为它可以 节约更多的内存。我们可以通过分离出永不更改的数据部分,将大多数类数据保留为 Clean Memory,应该怎么做呢?
在介绍优化方法之前,我们先来看一下,在类加载之后,类的结构会变成如何呢?

类加载.png

在类首次被使用的时候,runtime 会为它分配额外的存储容量,用于 读取/写入 数据的一个结构体 class_rw_t
在这个结构体中,存储了只有在运行时才会生成的新信息。例如:所有的类都会链接成一个树状结构,这是通过 firstSubclassnextSiblingClass指针实现的,这允许 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 的大小减少了一半, 所以,结构会变成这个样子。

类实际加载.png

大约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.21. 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.41.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
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享