1. 元类

将实例对象isa进行&运算,可得到类对象地址。同样类也是一个对象,也有自己的数据结构,如果将类对象的isa & ISA_MASK,会得到什么?

1.1 对象isa

打开main.m文件,写入以下代码:

  1. int main(int argc, const char * argv[]) {
  2. @autoreleasepool {
  3. LGPerson *p = [LGPerson alloc];
  4. NSLog(@"%@",p);
  5. }
  6. return 0;
  7. }

打印实例对象的数据结构

  1. x/4g p
  2. -------------------------
  3. 0x100547b50: 0x011d800100008365 0x0000000000000000
  4. 0x100547b60: 0x0000000000000000 0x0000000000000000
  • isa0x011d800100008365

得到类对象地址

  1. p/x 0x011d800100008365 & 0x00007ffffffffff8ULL
  2. -------------------------
  3. 0x0000000100008360

将地址进行po打印

  1. po 0x0000000100008360
  2. -------------------------
  3. LGPerson
  • 实例对象pisa,指向LGPerson

1.2 类的isa

打印LGPerson的数据结构

  1. x/4g 0x0000000100008360
  2. -------------------------
  3. 0x100008360: 0x0000000100008338 0x00007fff80670008
  4. 0x100008370: 0x0000000100712880 0x0002802c00000003
  • isa0x0000000100008338

类对象isa进行&运算

  1. p/x 0x0000000100008338 & 0x00007ffffffffff8ULL
  2. -------------------------
  3. 0x0000000100008338
  • 得到相同的地址

将地址进行po打印

  1. po 0x0000000100008338
  2. -------------------------
  3. LGPerson

分别使用实例对象和类对象的isa & ISA_MASK,得到的地址完全不同,但打印结果同样是LGPerson

  1. 0x100008360 = LGPerson = 0x100008338

此时我们有一个大胆的设想,同一个类在内存中不止有一个,它可能和对象一样,可以无限开辟

1.3 元类初探

  1. Class class1 = [LGPerson class];
  2. Class class2 = [LGPerson alloc].class;
  3. Class class3 = object_getClass([LGPerson alloc]);
  4. NSLog(@"class1:%p", class1);
  5. NSLog(@"class2:%p", class2);
  6. NSLog(@"class3:%p", class3);
  7. -------------------------
  8. //输出结果:
  9. class10x100008360
  10. class20x100008360
  11. class30x100008360
  • 测试结果很明显,真正的LGPerson类,地址为0x100008360

所以0x100008338又是什么?

OC中,类也是一个对象。既然是对象,也会存在isa指针,指向它所属的类。就是我们所说的元类 (MetaClass)

在代码中,我们无法找到元类的代码。它是由系统生成和编译的,可使用MachOView查看
image.png

  • 0x100008338:就是LGPerson类所属的元类

结论:

  • 实例对象isa→类isa→元类

扩展内容

为什么对象isa进行&运算,得到的地址不同。但类isa进行&运算,得到的地址相同?

  • 对象的isanonpointer类型,除了类对象地址,isa中包含了类信息、对象的引⽤计数等,所以对象isa进行&运算,得到的地址不同
  • 类的isa为非nonpointer类型,属于纯isa指针,只存储类对象的地址。我们很少听说一个类有引⽤计数、弱引用、是否释放等信息,所以类isa进行&运算,得到的地址相同

2. isa走位 & 继承链

2.1 isa走位

一个类的isa指向它的元类,那元类的isa又会指向谁?

2.1.1 LGPersonisa

上述案例中,LGPersonisa,指向元类0x100008338,那元类isa又会指向哪里?

打印元类的数据结构

  1. x/4g 0x0000000100008338
  2. -------------------------
  3. 0x100008338: 0x00007fff8066ffe0 0x00007fff8066ffe0
  4. 0x100008348: 0x00007fff20261aa0 0x0000e03500000000
  • 元类isa0x00007fff8066ffe0

元类isa进行&运算

  1. p/x 0x00007fff8066ffe0 & 0x00007ffffffffff8ULL
  2. -------------------------
  3. 0x00007fff8066ffe0
  • 得到相同的地址

将地址进行po打印

  1. po 0x00007fff8066ffe0
  2. -------------------------
  3. NSObject
  • 元类的isa指向NSObject

查看NSObject类的地址

  1. p/x NSObject.class
  2. -------------------------
  3. 0x00007fff80670008 NSObject
  • NSObject地址和LGPerson元类isa指向的NSObject地址不同

疑问:

  • 元类isa指向的地址到底是什么?

2.1.2 NSObjectisa

打印NSObject类的数据结构

  1. x/4g 0x00007fff80670008
  2. -------------------------
  3. 0x7fff80670008: 0x00007fff8066ffe0 0x0000000000000000
  4. 0x7fff80670018: 0x00000001005605a0 0x0001801000000003
  • NSObject作为根类,它的isa指向的元类,称之为根元类
  • 根元类的地址和LGPerson元类isa指向的地址相同
  • LGPerson的元类isa,指向的并不是NSObject,而是根元类(NSObject的元类)

打印根元类的数据结构

  1. x/4g 0x00007fff8066ffe0
  2. -------------------------
  3. 0x7fff8066ffe0: 0x00007fff8066ffe0 0x00007fff80670008
  4. 0x7fff8066fff0: 0x0000000100562a90 0x0005e03100000007
  • 根元类isa指向自己

结论:

  • LGPerson的元类isa,指向根元类(NSObject的元类)
  • NSObject作为根类,isa走位只有两层
  • 根类isa→根元类isa→自己

2.1.3 isa走位图

image.png

  • 实例对象isa→类isa→元类isa→根元类isa→自己
  • 根类的实例对象isa→根类isa→根元类isa→自己

2.2 继承链

任何一个类,都会有它的继承关系。那元类继承于谁,它的父类又会是谁?

2.2.1 LGPerson的继承链

LGPerson继承于NSObjectLGPerson元类的父类又会是谁?

打印NSObjectisa走位

  1. NSObject *obj = [NSObject alloc];
  2. Class metaClass = object_getClass(class);
  3. Class rootMetaClass = object_getClass(rootClass);
  4. NSLog(@"根类的实例对象:%p", obj);
  5. NSLog(@"根类:%p", rootClass);
  6. NSLog(@"根元类:%p", rootMetaClass);
  7. -------------------------
  8. //输出结果:
  9. 根类的实例对象:0x100571310
  10. 根类:0x7fff80670008
  11. 根元类:0x7fff8066ffe0

打印LGPerson元类的父类

  1. Class pMetaClass = object_getClass(LGPerson.class);
  2. Class pSuperClass = class_getSuperclass(pMetaClass);
  3. NSLog(@"LGPerson元类的父类:%@ - %p",pSuperClass,pSuperClass);
  4. -------------------------
  5. LGPerson元类的父类:NSObject - 0x7fff8066ffe0
  • 通过地址可以看出,LGPerson元类的父类并不是NSObject,而是NSObject的元类,即:根元类

元类的父类是根元类,这个结论正确吗?我们需要用层级更深的继承链去验证

2.2.2 LGTeacher的继承链

LGTeacher继承于LGPersonLGPerson继承于NSObject,打印LGTeacher的继承链

打印LGPersonisa走位

  1. Class pMetaClass = object_getClass(LGPerson.class);
  2. Class pRootMetaClass = object_getClass(pMetaClass);
  3. NSLog(@"LGPerson元类:%p", pMetaClass);
  4. NSLog(@"LGPerson根元类:%p", pRootMetaClass);
  5. -------------------------
  6. //输出结果:
  7. LGPerson元类:0x100008338
  8. LGPerson根元类:0x7fff8066ffe0

打印LGTeacher元类的父类

  1. Class tMetaClass = object_getClass(LGTeacher.class);
  2. Class tSuperClass = class_getSuperclass(tMetaClass);
  3. NSLog(@"LGTeacher元类的父类:%@ - %p",tSuperClass,tSuperClass);
  4. -------------------------
  5. LGTeacher元类的父类:LGPerson - 0x100008338
  • LGTeacher元类的父类,指向其父类LGPerson的元类

结论:

  • 元类的父类是根元类,结论显然是错误的
  • 元类→父类的元类→根元类

2.2.3 NSObject的继承链

NSObject作为根类,它的继承链有些特殊

打印根类和根元类

  1. Class rootClass = NSObject.class;
  2. Class rootMetaClass = object_getClass(rootClass);
  3. NSLog(@"根类:%p", rootClass);
  4. NSLog(@"根元类:%p", rootMetaClass);
  5. -------------------------
  6. //输出结果:
  7. 根类:0x7fff80670008
  8. 根元类:0x7fff8066ffe0

打印根类的父类和根元类的父类

  1. Class nSuperClass = class_getSuperclass(rootClass);
  2. Class rnSuperClass = class_getSuperclass(rootMetaClass);
  3. NSLog(@"NSObject的父类:%@ - %p",nSuperClass,nSuperClass);
  4. NSLog(@"根元类的父类:%@ - %p",rnSuperClass,rnSuperClass);
  5. -------------------------
  6. //输出结果:
  7. 根类的父类:(null) - 0x0
  8. 根元类的父类:NSObject - 0x7fff80670008
  • NSObject作为根类,没有父类
  • 根元类的父类,指向根类,即:NSObject

总结:

  • 根类的继承链:根类→nil
  • 根元类的继承链:根元类→根类→nil

2.2.4 继承链流程图

image.png

  • 类的继承链:类→父类→根类→nil
  • 元类的继承链:元类→父类的元类→根元类→根类→nil
  • 继承关系只来自于类,对象之间没有这层关系
  • NSObject作为根类,它才是真正的万物之主,所有类都源于NSObject

2.3 官方流程图

image.png

  • isa走位:实例对象isa→类isa→元类isa→根元类isa→自己
  • 类的继承链:类→父类→根类→nil
  • 元类的继承链:元类→父类的元类→根元类→根类→nil

3. 类的结构

类也是一个对象,有自己的数据结构,而类的底层来自objc_class结构体,可通过源码分析类的结构

3.1 源码分析

objc4-818.2源码中,搜索struct objc_class关键字,跳过一些过时的代码。例如:objc-runtime-old.h中的定义,以及runtime.h中的老旧代码

objc-runtime-new.h中,找到objc_class结构体的定义:

  1. struct objc_class : objc_object {
  2. objc_class(const objc_class&) = delete;
  3. objc_class(objc_class&&) = delete;
  4. void operator=(const objc_class&) = delete;
  5. void operator=(objc_class&&) = delete;
  6. // Class ISA;
  7. Class superclass;
  8. cache_t cache; // formerly cache pointer and vtable
  9. class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
  10. ...
  11. };
  • objc_class继承自objc_object

结构体objc_class下,除objc_objectisa之外,还包含自身的superclasscachebits三个成员变量
image.png

  • superclass为结构体指针,存储父类
  • cachecache_t结构体,存储方法缓存
  • bitsclass_data_bits_t结构体,存储方法、属性、协议列表等

疑问:

  • objc_class结构体中,如何获取到bits的数据?

3.2 内存平移

内存中读取数据,可以使用内存平移的方式。在首地址的基础上,偏移出数据的大小,即可将其完整读取

3.2.1 普通指针

  1. int a = 10;
  2. int b = 10;
  3. NSLog(@"a:%d,%p", a, &a);
  4. NSLog(@"b:%d,%p", b, &b);
  5. -------------------------
  6. //输出结果:
  7. a100x7ffeefbff3ac
  8. b100x7ffeefbff3a8
  • &a&b的地址不同,但都指向相同的值10。这就是我们常说的值拷贝,即:深拷贝
  • &a&b的地址是连续的,由于是int类型,它们相差4字节

3.2.2 对象指针

  1. LGPerson *p1 = [LGPerson alloc];
  2. LGPerson *p2 = [LGPerson alloc];
  3. NSLog(@"p1:%@,%p", p1, &p1);
  4. NSLog(@"p2:%@,%p", p2, &p2);
  5. -------------------------
  6. //输出结果:
  7. p1:<LGPerson: 0x101088280>,0x7ffeefbff3a8
  8. p2:<LGPerson: 0x101088450>,0x7ffeefbff3a0
  • p1p2是指针,指向堆区开辟的内存空间
  • &p1&p2是指向p1p2对象指针的地址,俗称:二级指针

对象指针和普通指针的区别:
image.png

3.2.3 数组指针

  1. int c[4] = {1,2,3,4};
  2. int *d = c;
  3. NSLog(@"c:%p,%p,%p,%p", &c, &c[0], &c[1], &c[2]);
  4. NSLog(@"d:%p,%p,%p,%p", d, d+0, d+1, d+2);
  5. -------------------------
  6. //输出结果:
  7. c0x7ffeefbff3c00x7ffeefbff3c00x7ffeefbff3c40x7ffeefbff3c8
  8. d0x7ffeefbff3c00x7ffeefbff3c00x7ffeefbff3c40x7ffeefbff3c8
  • 数组中索引0元素的地址,即是数组的首地址
  • 数组中元素的地址是连续的,由于是int类型数组,每个元素的地址之间相差4字节
  • 读取元素的方式,也可以使用内存平移的方式。在首地址上,移动指定步长即可

数组中的元素指向:image.png

使用内存平移的方式,遍历数组中的元素

  1. for (int i = 0; i<4; i++) {
  2. int value = *(d+i);
  3. NSLog(@"索引%d:%d", i, value);
  4. }
  5. -------------------------
  6. //输出结果:
  7. 索引01
  8. 索引12
  9. 索引23
  10. 索引34

3.3 内存计算

按照内存平移的原理,从objc_class中获取bits数据,需要跨过isasuperclasscache三个成员变量的大小之和

3.3.1 superclass

isasuperclass都是Class类型,本质是结构体指针,占8字节。其中superclass,从字面上看,应该存储的是其父类

验证superclass存储的数据:

LGPerson继承自NSObject

  1. @interface LGPerson : NSObject
  2. @end

打印LGPerson类的数据结构

  1. x/4g LGPerson.class
  2. -------------------------
  3. 0x100008330: 0x0000000100008358 0x000000010036a140
  4. 0x100008340: 0x0000000100362390 0x0000803400000000

打印第二个8字节数据

  1. po 0x000000010036a140
  2. -------------------------
  3. NSObject

打印NSObject地址进行对比

  1. p/x NSObject.class
  2. -------------------------
  3. 0x000000010036a140 NSObject
  • 从打印结果来看,superclassNSObject地址一致,说明superclass存储的的确是其父类

3.3.2 cache

cachecache_t结构体类型,存储方法缓存,它在内存中占用多少空间呢?

找到cache_t结构体的定义

  1. struct cache_t {
  2. private:
  3. explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
  4. union {
  5. struct {
  6. explicit_atomic<mask_t> _maybeMask;
  7. #if __LP64__
  8. uint16_t _flags;
  9. #endif
  10. uint16_t _occupied;
  11. };
  12. explicit_atomic<preopt_cache_t *> _originalPreoptCache;
  13. };
  14. ...
  15. };
  • 影响结构体大小,只有_bucketsAndMaybeMaskunion
  • 方法和函数,不影响结构体大小
  • static成员,存储在MachO的全局区,也不影响结构体大小

_bucketsAndMaybeMask

  • 泛型,大小取决于传入的数据类型
  • uintptr_t类型,无符号整数,能够存储指针,占8字节

union

  • struct

_maybeMask:泛型,传入的mask_tuint32_t类型,占4字节
_flagsuint16_t类型,占2字节
_occupieduint16_t类型,占2字节
struct,共计8字节

  • _originalPreoptCache:结构体指针,占8字节

struct中的mask_t定义

  1. typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits

联合体特性,成员之间内存共用,大小为最大成员变量的大小。故此,union大小,占8字节


结论:

  • cache_t结构体的大小:uintptr_t8字节) + union8字节) = 16字节

3.4 结构分析

得到isasuperclasscache的大小,使用内存平移,可获取bits成员变量

bitsclass_data_bits_t结构体类型,通过定义不难看出,核心方法存储在class_rw_t结构体中

  1. struct class_data_bits_t {
  2. friend objc_class;
  3. // Values are the FAST_ flags above.
  4. uintptr_t bits;
  5. ...
  6. class_rw_t* data() const {
  7. return (class_rw_t *)(bits & FAST_DATA_MASK);
  8. }
  9. ...
  10. };
  • 使用friend定义objc_class为友元,内部的私钥方法也可使用

3.4.1 准备工作

以下探索过程,必须在可编译的objc源码中进行

打开LGPerson.h文件,写入以下代码:

  1. @interface LGPerson : NSObject{
  2. NSString *desc;
  3. }
  4. @property (strong,nonatomic) NSString *name;
  5. @property (strong,nonatomic) NSString *nick;
  6. -(void)sayNB;
  7. +(void)good;
  8. @end
  • 一个成员变量desc
  • 两个属性namenick
  • 一个实例方法sayNB
  • 一个类方法good

打开LGPerson.m文件,写入以下代码:

  1. @implementation LGPerson
  2. - (instancetype)init
  3. {
  4. self = [super init];
  5. if (self) {
  6. }
  7. return self;
  8. }
  9. -(void)sayNB{
  10. }
  11. +(void)good{
  12. }
  13. @end

3.4.2 class_rw_t

找到class_rw_t中的核心方法

打印LGPerson的首地址

  1. p/x LGPerson.class
  2. -------------------------
  3. 0x0000000100008330 LGPerson

内存平移:首地址 + 32字节

  1. p/x 0x0000000100008330 + 0x20
  2. -------------------------
  3. 0x0000000100008350

得到bits地址,但我们并不能直接打印地址中的数据结构,需将其转为结构体指针

  1. p (class_data_bits_t *)0x0000000100008350
  2. -------------------------
  3. (class_data_bits_t *) $12 = 0x0000000100008350

获取class_rw_t,来自于class_data_bits_tdata函数。因为是结构体指针,使用->指向。如果是结构体,可以直接使用.

  1. p $12->data()
  2. -------------------------
  3. (class_rw_t *) $15 = 0x0000000100627860

使用*取值,打印class_rw_t的数据结构

  1. p *$15
  2. -------------------------
  3. (class_rw_t) $16 = {
  4. flags = 2148007936
  5. witness = 0
  6. ro_or_rw_ext = {
  7. std::__1::atomic<unsigned long> = {
  8. Value = 4295000120
  9. }
  10. }
  11. firstSubclass = nil
  12. nextSiblingClass = NSUUID
  13. }

通过打印,我们看不出class_rw_t中的数据结构。这种情况,只能从源码分析入手

找到class_rw_t的定义

  1. struct class_rw_t {
  2. ...
  3. const method_array_t methods() const {
  4. auto v = get_ro_or_rwe();
  5. if (v.is<class_rw_ext_t *>()) {
  6. return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
  7. } else {
  8. return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods()};
  9. }
  10. }
  11. const property_array_t properties() const {
  12. auto v = get_ro_or_rwe();
  13. if (v.is<class_rw_ext_t *>()) {
  14. return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
  15. } else {
  16. return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
  17. }
  18. }
  19. const protocol_array_t protocols() const {
  20. auto v = get_ro_or_rwe();
  21. if (v.is<class_rw_ext_t *>()) {
  22. return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;
  23. } else {
  24. return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
  25. }
  26. }
  27. };
  • 找到了存储方法、属性、协议列表的三个核心方法

3.4.3 properties

获取LGPerson类中的属性列表

  1. p $1->properties()
  2. -------------------------
  3. (const property_array_t) $2 = {
  4. list_array_tt<property_t, property_list_t, RawPtr> = {
  5. = {
  6. list = {
  7. ptr = 0x0000000100008160
  8. }
  9. arrayAndFlag = 4295000416
  10. }
  11. }
  12. }
  • $1:结构体指针class_rw_t *

找到property_array_t的定义

  1. class property_array_t :
  2. public list_array_tt<property_t, property_list_t, RawPtr>
  3. {
  4. typedef list_array_tt<property_t, property_list_t, RawPtr> Super;
  5. public:
  6. property_array_t() : Super() { }
  7. property_array_t(property_list_t *l) : Super(l) { }
  8. };
  • 继承自list_array_tt

找到list_array_tt的定义

  1. template <typename Element, typename List, template<typename> class Ptr>
  2. class list_array_tt {
  3. struct array_t {
  4. uint32_t count;
  5. Ptr<List> lists[0];
  6. ...
  7. };
  8. protected:
  9. class iterator {
  10. const Ptr<List> *lists;
  11. const Ptr<List> *listsEnd;
  12. typename List::iterator m, mEnd;
  13. ...
  14. };
  15. ...
  16. };
  • list_array_tt<Element, List, Ptr>为模板类
  • 包含迭代器(iterate)的方法,具有遍历特性

打印property_array_t下的list

  1. p $2.list
  2. -------------------------
  3. (const RawPtr<property_list_t>) $3 = {
  4. ptr = 0x0000000100008160
  5. }

打印ptr

  1. p $3.ptr
  2. -------------------------
  3. (property_list_t *const) $4 = 0x0000000100008160

使用*取值,打印property_list_t的数据结构

  1. p *$4
  2. -------------------------
  3. (property_list_t) $5 = {
  4. entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 2)
  5. }

找到property_list_t的定义

  1. struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
  2. };
  • 继承自entsize_list_tt结构体

找到entsize_list_tt的定义

  1. template <typename Element, typename List, uint32_t FlagMask, typename PointerModifier = PointerModifierNop>
  2. struct entsize_list_tt {
  3. uint32_t entsizeAndFlags;
  4. uint32_t count;
  5. ...
  6. Element& get(uint32_t i) const {
  7. ASSERT(i < count);
  8. return getOrEnd(i);
  9. }
  10. ...
  11. };
  • 成员变量count,可获取属性总数
  • get函数,传入索引,可获取指定属性

打印属性总数

  1. p $5.count
  2. -------------------------
  3. (uint32_t) $6 = 2
  • 共计两个属性

打印属性1

  1. p $5.get(0)
  2. -------------------------
  3. (property_t) $7 = (name = "name", attributes = "T@\"NSString\",&,N,V_name")

打印属性2

  1. p $5.get(1)
  2. -------------------------
  3. (property_t) $8 = (name = "nick", attributes = "T@\"NSString\",&,N,V_nick")

找到property_t的定义

  1. struct property_t {
  2. const char *name;
  3. const char *attributes;
  4. };
  • 结构体中定义了nameattributes成员变量,可直接打印

疑问:

  • LGPerson中的desc成员变量在哪里?

3.4.4 methods

获取LGPerson类中的方法列表

  1. p $1->methods()
  2. -------------------------
  3. (const method_array_t) $2 = {
  4. list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
  5. = {
  6. list = {
  7. ptr = 0x0000000100008080
  8. }
  9. arrayAndFlag = 4295000192
  10. }
  11. }
  12. }

打印method_array_t下的list.ptr

  1. p $2.list.ptr
  2. -------------------------
  3. (method_list_t *const) $3 = 0x0000000100008080

使用*取值,打印method_list_t的数据结构

  1. p *$3
  2. -------------------------
  3. (method_list_t) $4 = {
  4. entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 6)
  5. }

找到method_list_t的定义

  1. struct method_list_t : entsize_list_tt<method_t, method_list_t, 0xffff0003, method_t::pointer_modifier> {
  2. ...
  3. };
  • 继承自entsize_list_tt结构体

打印方法总数

  1. p $4.count
  2. -------------------------
  3. (uint32_t) $5 = 7
  • 共计七个方法

打印方法1

  1. p $4.get(0)
  2. -------------------------
  3. (method_t) $6 = {}
  • 遇到小问题,打印结果为空

找到method_t的定义

  1. struct method_t {
  2. ...
  3. struct big {
  4. SEL name;
  5. const char *types;
  6. MethodListIMP imp;
  7. };
  8. ...
  9. big &big() const {
  10. ASSERT(!isSmall());
  11. return *(struct big *)this;
  12. }
  13. ...
  14. objc_method_description *getDescription() const {
  15. return isSmall() ? getSmallDescription() : (struct objc_method_description *)this;
  16. }
  17. ...
  18. };
  • 结构体内没有定义成员变量,故此打印结果为空
  • 通过big()getDescription()打印方法信息

打印LGPerson下的所有方法

  1. p *($4.get(0).getDescription())
  2. p *($4.get(1).getDescription())
  3. p *($4.get(2).getDescription())
  4. p *($4.get(3).getDescription())
  5. p *($4.get(4).getDescription())
  6. p *($4.get(5).getDescription())
  7. p *($4.get(6).getDescription())
  8. -------------------------
  9. //打印结果:
  10. (objc_method_description) $7 = (name = "sayNB", types = "v16@0:8")
  11. (objc_method_description) $8 = (name = "nick", types = "@16@0:8")
  12. (objc_method_description) $9 = (name = "setNick:", types = "v24@0:8@16")
  13. (objc_method_description) $10 = (name = "init", types = "@16@0:8")
  14. (objc_method_description) $11 = (name = "name", types = "@16@0:8")
  15. (objc_method_description) $12 = (name = ".cxx_destruct", types = "v16@0:8")
  16. (objc_method_description) $13 = (name = "setName:", types = "v24@0:8@16")
  • 方法1sayNB实例方法
  • 方法2nick属性的getter方法
  • 方法3nick属性的setter方法
  • 方法4init方法
  • 方法5name属性的getter方法
  • 方法6C++的析构方法
  • 方法7name属性的setter方法

疑问:

  • LGPerson中的good类方法在哪里?

3.4.5 protocols

定义LGPersonProtocol协议

  1. @protocol LGPersonProtocol
  2. -(void)lalala;
  3. @end

LGPerson遵循协议

  1. @interface LGPerson : NSObject<LGPersonProtocol>
  2. @end

获取LGPerson类中的协议列表

  1. p $17->protocols()
  2. -------------------------
  3. (const protocol_array_t) $18 = {
  4. list_array_tt<unsigned long, protocol_list_t, RawPtr> = {
  5. = {
  6. list = {
  7. ptr = 0x00000001000080c8
  8. }
  9. arrayAndFlag = 4295000264
  10. }
  11. }
  12. }

打印protocol_array_t下的list.ptr

  1. p $18.list.ptr
  2. -------------------------
  3. (protocol_list_t *const) $19 = 0x00000001000080c8

使用*取值,打印protocol_list_t的数据结构

  1. p *$19
  2. -------------------------
  3. (protocol_list_t) $20 = (count = 1, list = protocol_ref_t [] @ 0x00007fac118979e8)

找到protocol_list_t的定义

  1. struct protocol_list_t {
  2. // count is pointer-sized by accident.
  3. uintptr_t count;
  4. protocol_ref_t list[0]; // variable-size
  5. ...
  6. };

找到protocol_ref_t的定义

  1. typedef uintptr_t protocol_ref_t; // protocol_t *, but unremapped

疑问:

protocol_list_t结构体中,只有countprotocol_ref_t类型的list,其中也没有任何获取协议信息的方法。而protocol_ref_t结构体,我们在源码中,只找到了它的定义。此时问题来了,我们怎么才能输出协议信息?


在源码中,搜索protocol_ref_t关键字

找到这样一个方法,传入protocol_ref_t结构体,返回protocol_t结构体指针

  1. static ALWAYS_INLINE protocol_t *remapProtocol(protocol_ref_t proto)
  2. {
  3. runtimeLock.assertLocked();
  4. // Protocols in shared cache images have a canonical bit to mark that they
  5. // are the definition we should use
  6. if (((protocol_t *)proto)->isCanonical())
  7. return (protocol_t *)proto;
  8. protocol_t *newproto = (protocol_t *)
  9. getProtocol(((protocol_t *)proto)->mangledName);
  10. return newproto ? newproto : (protocol_t *)proto;
  11. }
  • 核心代码:直接将protocol_ref_t强转为protocol_t结构体指针

找到protocol_t的定义

  1. struct protocol_t : objc_object {
  2. const char *mangledName;
  3. struct protocol_list_t *protocols;
  4. method_list_t *instanceMethods;
  5. method_list_t *classMethods;
  6. method_list_t *optionalInstanceMethods;
  7. method_list_t *optionalClassMethods;
  8. property_list_t *instanceProperties;
  9. ...
  10. };
  • 所有协议信息都在里面

继续lldb调试

protocol_list_t强转为protocol_t结构体指针

  1. p (protocol_t *)$20.list[0]
  2. -------------------------
  3. (protocol_t *) $21 = 0x0000000100008398

使用*取值,打印protocol_t的数据结构

  1. p *$21
  2. -------------------------
  3. (protocol_t) $22 = {
  4. objc_object = {
  5. isa = {
  6. bits = 4298547400
  7. cls = Protocol
  8. = {
  9. nonpointer = 0
  10. has_assoc = 0
  11. has_cxx_dtor = 0
  12. shiftcls = 537318425
  13. magic = 0
  14. weakly_referenced = 0
  15. unused = 0
  16. has_sidetable_rc = 0
  17. extra_rc = 0
  18. }
  19. }
  20. }
  21. mangledName = 0x0000000100003ed0 "LGPersonProtocol"
  22. protocols = 0x0000000000000000
  23. instanceMethods = 0x0000000100008038
  24. classMethods = 0x0000000000000000
  25. optionalInstanceMethods = 0x0000000000000000
  26. optionalClassMethods = 0x0000000000000000
  27. instanceProperties = 0x0000000000000000
  28. size = 96
  29. flags = 0
  30. _extendedMethodTypes = 0x0000000100008058
  31. _demangledName = 0x0000000000000000
  32. _classProperties = 0x0000000000000000
  33. }
  • 成功打印协议信息

协议中的instanceMethodsmethod_list_t结构体指针类型,打印其中的方法

  1. p ((method_list_t *)0x0000000100008038).get(0).big()
  2. -------------------------
  3. (method_t::big) $31 = {
  4. name = "lalala"
  5. types = 0x0000000100003eec "v16@0:8"
  6. imp = 0x0000000000000000
  7. }

4. 成员变量

使用properties获取LGPerson的属性列表,但是desc成员变量存储在哪里呢?

WWDC曾经提到过,成员变量Ivars存储在class_ro_t
image.png

4.1 class_ro_t

找到class_rw_t的定义

  1. struct class_rw_t {
  2. ...
  3. const class_ro_t *ro() const {
  4. auto v = get_ro_or_rwe();
  5. if (slowpath(v.is<class_rw_ext_t *>())) {
  6. return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
  7. }
  8. return v.get<const class_ro_t *>(&ro_or_rw_ext);
  9. }
  10. ...
  11. };
  • 通过ro函数,得到class_ro_t

4.2 Ivars

获取LGPerson类中的class_ro_t

  1. p $1->ro()
  2. -------------------------
  3. (const class_ro_t *) $2 = 0x0000000100008038

使用*取值,打印class_ro_t的数据结构

  1. p *$2
  2. -------------------------
  3. (const class_ro_t) $3 = {
  4. flags = 388
  5. instanceStart = 8
  6. instanceSize = 32
  7. reserved = 0
  8. = {
  9. ivarLayout = 0x0000000100003f22 "\x03"
  10. nonMetaclass = 0x0000000100003f22
  11. }
  12. name = {
  13. std::__1::atomic<const char *> = "LGPerson" {
  14. Value = 0x0000000100003f24 "LGPerson"
  15. }
  16. }
  17. baseMethodList = 0x0000000100008080
  18. baseProtocols = 0x0000000000000000
  19. ivars = 0x0000000100008130
  20. weakIvarLayout = 0x0000000000000000
  21. baseProperties = 0x0000000100008198
  22. _swiftMetadataInitializer_NEVER_USE = {}
  23. }
  • ivars:成员变量列表

ivars转为结构体指针

  1. p (ivar_list_t *)0x0000000100008130
  2. -------------------------
  3. (ivar_list_t *) $4 = 0x0000000100008130

使用*取值,打印ivar_list_t的数据结构

  1. p *$4
  2. -------------------------
  3. (ivar_list_t) $5 = {
  4. entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 3)
  5. }

找到ivar_list_t的定义

  1. struct ivar_list_t : entsize_list_tt<ivar_t, ivar_list_t, 0> {
  2. bool containsIvar(Ivar ivar) const {
  3. return (ivar >= (Ivar)&*begin() && ivar < (Ivar)&*end());
  4. }
  5. };
  • 继承自entsize_list_tt结构体

打印成员变量总数

  1. p $5.count
  2. -------------------------
  3. (uint32_t) $6 = 3
  • 共计三个成员变量

打印成员变量1

  1. p $5.get(0)
  2. -------------------------
  3. (ivar_t) $7 = {
  4. offset = 0x0000000100008240
  5. name = 0x0000000100003f2d "desc"
  6. type = 0x0000000100003f78 "@\"NSString\""
  7. alignment_raw = 3
  8. size = 8
  9. }

打印成员变量2

  1. p $5.get(1)
  2. -------------------------
  3. (ivar_t) $8 = {
  4. offset = 0x0000000100008248
  5. name = 0x0000000100003f32 "_name"
  6. type = 0x0000000100003f78 "@\"NSString\""
  7. alignment_raw = 3
  8. size = 8
  9. }

打印成员变量3

  1. p $5.get(2)
  2. -------------------------
  3. (ivar_t) $9 = {
  4. offset = 0x0000000100008250
  5. name = 0x0000000100003f38 "_nick"
  6. type = 0x0000000100003f78 "@\"NSString\""
  7. alignment_raw = 3
  8. size = 8
  9. }

总结:

  • 成员变量存储在class_ro_t结构体中

5. 类方法

使用methods获取LGPerson的方法列表,但是good类方法存储在哪里呢?

5.1 思考

实例方法也称为对象方法,它存储在实例对象所属的类中。这种设计方式,可以避免多个对象之间存储相同的方法列表,导致内存空间的浪费

如果类方法,也存储在类对象中,这样合理吗?

OC中,实例方法和类方法,在底层的实现都函数。同一个类中,允许出现同名的实例方法和类方法。如果它们都存储在类对象中,底层该如何区分这些同名方法呢?

所以,类方法存储在类对象中,显然是不合理的

我们都知道,类的本质也是对象。故此,类方法存储在类对象所属的元类中,这样设计更为合理

这也是系统提供元类的原因之一

5.2 探索

打印LGPerson的数据结构

  1. x/4g LGPerson.class
  2. -------------------------
  3. 0x100008290: 0x00000001000082b8 0x000000010036a140
  4. 0x1000082a0: 0x0000000100362390 0x0000803400000000
  • isa0x00000001000082b8

打印类的isa指向的元类

  1. p/x 0x00000001000082b8 & 0x00007ffffffffff8ULL
  2. -------------------------
  3. 0x00000001000082b8

打印元类的bits地址

  1. p (class_data_bits_t *)(0x00000001000082b8 + 0x20)
  2. -------------------------
  3. (class_data_bits_t *) $17 = 0x00000001000082d8

打印元类的class_rw_t

  1. p $17->data()
  2. -------------------------
  3. (class_rw_t *) $18 = 0x000000010062c250

获取元类的方法列表

  1. p $18->methods()
  2. -------------------------
  3. (const method_array_t) $19 = {
  4. list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
  5. = {
  6. list = {
  7. ptr = 0x0000000100008238
  8. }
  9. arrayAndFlag = 4295000632
  10. }
  11. }
  12. }

打印method_array_t下的list.ptr

  1. p $19.list.ptr
  2. -------------------------
  3. (method_list_t *const) $20 = 0x0000000100008238

使用*取值,打印method_list_t的数据结构

  1. p *$20
  2. -------------------------
  3. (method_list_t) $21 = {
  4. entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 1)
  5. }

打印方法总数

  1. p $21.count
  2. -------------------------
  3. (uint32_t) $22 = 1
  • 只有一个方法

打印方法1

  1. p $21.get(0).big()
  2. -------------------------
  3. (method_t::big) $23 = {
  4. name = "good"
  5. types = 0x0000000100003f8c "v16@0:8"
  6. imp = 0x0000000100003e10 (KCObjcBuild`+[LGPerson good])
  7. }

总结:

  • OC中,实例方法和类方法,在底层的实现都函数
  • 类方法存储在类对象所属的元类中

总结

元类

  • 类也是一个对象,也存在isa指针,指向它所属的类。即:元类 (MetaClass)
  • 元类初探:实例对象isa→类isa→元类
  • 对象的isanonpointer类型,除了类对象地址,isa中包含类信息,所以&运算后地址不同
  • 类的isa为非nonpointer类型,属于纯isa指针,只存储类对象的地址,所以&运算后地址相同

isa走位图

  • NSObject为根类,NSObject的元类为根元类
  • 实例对象isa→类isa→元类isa→根元类isa→自己
  • 根类的实例对象isa→根类isa→根元类isa→自己

继承链

  • 继承关系只来自于类,对象之间没有这层关系
  • 类和元类都存在继承链
  • 元类的父类是父类的元类
  • 类的继承链:类→父类→根类→nil
  • 元类的继承链:元类→父类的元类→根元类→根类→nil
  • NSObject作为根类,它才是真正的万物之主,所有类都源于NSObject

类的结构

  • objc_class继承自objc_object
  • objc_objectisa之外,还包含superclasscachebits
  • superclass存储父类
  • cache存储方法缓存
  • bits存储方法、属性、协议列表等

内存平移

  • 普通指针

&a&b的地址不同,但都指向相同的值,即:值拷贝(深拷贝)
&a&b的地址是连续的,由于是int类型,它们相差4字节

  • 对象指针

p1p2是指针,指向堆区开辟的内存空间
&p1&p2是指向p1p2对象指针的地址,俗称:二级指针

  • 数组指针

◦ 数组中索引0元素的地址,即是数组的首地址
◦ 数组中元素的地址是连续的,由于是int类型数组,每个元素的地址之间相差4字节
◦ 读取元素的方式,也可以使用内存平移的方式。在首地址上,移动指定步长即可

内存计算

  • isasuperclass本质是结构体指针,占8字节
  • cachecache_t结构体类型
  • cache_t结构体的大小,uintptr_t8字节) + union8字节) = 16字节

类的结构分析

  • bitsclass_data_bits_t结构体,存储方法、属性、协议列表等
  • 核心方法在class_rw_t结构体中

properties:可获取属性列表,但不包含成员变量
methods:可获取方法列表,但不包含类方法
protocols:可获取协议列表

成员变量

  • 成员变量存储在class_ro_t结构体中

类方法

  • OC中,实例方法和类方法,在底层的实现都函数
  • 类方法存储在类对象所属的元类中