iOS底层--类的原理分析(上)

285 阅读6分钟

ISA分析到元类

之前文章iOS底层--对象本质&ISA分析探究了isa

image.png

打印p的指针 0x00000001089c94f0,根据指针地址获取isa 0x011d800100008365 进行掩码内存偏移 0x00007ffffffffff8 得到 0x0000000100008360po获取当前的类 XHPerson

image.png

对得到的 0x0000000100008360继续分析,进行内存偏移 0x00007ffffffffff8 得到 0x0000000100008338po获取当前的类也是 XHPerson。可以得出XHPerson:类会和我们的对象,无限开辟,内存不止有一个类。验证打印下面代码:

//MARK: - 分析类对象内存存在个数
void lgTestClassNum(void){
    Class class1 = [XHPerson class];
    Class class2 = [XHPerson alloc].class;
    Class class3 = object_getClass([XHPerson alloc]);
    Class class4 = [XHPerson alloc].class;
    NSLog(@"\n%p-\n%p-\n%p-\n%p",class1,class2,class3,class4);
}

// 结果
2021-06-21 15:23:32.247341+0800 002-isa分析[13938:273705] 
0x100008360-
0x100008360-
0x100008360-
0x100008360

从打印来看 0x0000000100008360是类,0x0000000100008338不是类。它就是元类

image.png

元类进行x/4gx得到 0x00007fff88a2ac20 继续进行内存偏移 0x00007ffffffffff8,得到还是 0x00007fff88a2ac20,po得到 NSObject

image.png 意味着当前的 NSObject 这个类的 isa 指向 NSObject元类 。根据上面的分析可以的得出。

isa的走位图和继承链

  • 对象 isa -> 类 isa -> 元类 isa -> 根元类 isa -> 根元类
  • 根类 isa -> 根元类 isa image.png

isa走位

  • 实例对象instance of Subclass 的 isa 指向 class。
  • class 的 isa 指向 元类 meta class。
  • 元类 meta class 的 isa 指向 根元类 NSObject(root metal class)。
  • 根元类 NSObject root meta class 的 isa 指向自己本身,形成闭环。

打印下面代码,

#pragma mark - NSObject 元类链
void lgTestNSObject(void){
    // NSObject实例对象
    NSObject *object1 = [NSObject alloc];
    // NSObject类
    Class class = object_getClass(object1);
    // NSObject元类
    Class metaClass = object_getClass(class);
    // NSObject根元类
    Class rootMetaClass = object_getClass(metaClass);
    // NSObject根根元类
    Class rootRootMetaClass = object_getClass(rootMetaClass);
    NSLog(@"\n%p 实例对象\n%p 类\n%p 元类\n%p 根元类\n%p 根根元类",object1,class,metaClass,rootMetaClass,rootRootMetaClass);
    
    // XHPerson元类
    Class pMetaClass = object_getClass(XHPerson.class);
    Class psuperClass = class_getSuperclass(pMetaClass);
    NSLog(@"XHPerson元类  %@ - %p",psuperClass,psuperClass);
    
    // XHTeacher -> XHPerson -> NSObject
    // 元类也有一条继承链
    Class tMetaClass = object_getClass(XHTeacher.class);
    Class tsuperClass = class_getSuperclass(tMetaClass);
    NSLog(@"XHTeacher元类  %@ - %p",tsuperClass,tsuperClass);

    // NSObject 根类特殊情况
    Class nsuperClass = class_getSuperclass(NSObject.class);
    NSLog(@"NSObject元类  %@ - %p",nsuperClass,nsuperClass);
    // 根元类 -> NSObject
    Class rnsuperClass = class_getSuperclass(metaClass);
    NSLog(@"根元类  %@ - %p",rnsuperClass,rnsuperClass);
}

// 结果
2021-06-21 16:02:30.535430+0800 002-isa分析[14411:327183] 
0x108b06850 实例对象
0x7fff88a2ac48 类
0x7fff88a2ac20 元类
0x7fff88a2ac20 根元类
0x7fff88a2ac20 根根元类
2021-06-21 16:02:30.535501+0800 002-isa分析[14411:327183] XHPerson元类  NSObject - 0x7fff88a2ac20
2021-06-21 16:02:30.535531+0800 002-isa分析[14411:327183] XHTeacher元类  XHPerson - 0x100008338
2021-06-21 16:02:30.535554+0800 002-isa分析[14411:327183] NSObject元类  (null) - 0x0
2021-06-21 16:02:30.535576+0800 002-isa分析[14411:327183] 根元类  NSObject - 0x7fff88a2ac48

image.png

苹果官方提供的图

image.png

superclass继承

类的继承

  • subClass继承自 父类 superClass 。
  • 父类 superClass 继承自根类 NSObject rootClass。
  • 根类 NSObject 继承自 nil,这也是为什么NSObject是根类。

元类的继承

  • 子类的元类 metal subClass 继承自 父类的元类 metal superClass。
  • 父类的元类 metal superClass 继承自 根元类 root metal class。
  • 根元类 root metal class 继承自根类NSObject root class。

类的结构

内存偏移

分析类的结构之前,需要了解内存偏移,想要拿到类的内容时,需要通过内存偏移去获取。

普通指针

image.png

  • ab都指向10,但是ab的地址不一样,地址之间相差4字节,因为ab都是int类型。

对象指针

image.png

  • p1p2也是指针,分别指向 XHPerson alloc 创建的内存地址。
  • &p1&p2是指向p1p2对象指针的地址。

数组指针

image.png

  • &c&c[0]取出来的都是首地址。
  • &c&c[1]相差4个字节(地址之间差值,取决于存储的数据类型)。
  • 通过首地址+偏移量可以取出数组中的元素,偏移量也就是我们常说的数组下标
  • 内存中首地址移动的字节数 = 偏移量 * 数据类型字节数

类结构分析

通过objc源码,可以大致知道objc_class内部大致有哪些东西,如下图所示: image.png

  • isa:虽然此处注释了,但是objc_class继承自objc_object,所以objc_class肯定也是有isa的,占8字节。
  • superclassClass类型,本质是一个结构体指针,占8字节。
  • cache:是cache_t类型,通过源码我们可以知道cache_t是结构体类型,结构体类型的内存大小是由其内部的属性来决定的。
  • bits:是class_data_bits_t类型,无法获取其具体内存大小,只能通过上面说的内存偏移去获取bits

cache内存大小计算

进到cache_t看看

image.png 发现cache_t是一个结构体,除去static修饰的属性,其中影响其内存大小的只有上面截图中的两个:_bucketsAndMaybeMask和联合体 我们可以得出cache_t这个结构体的内存大小为8+8=16字节。

类里面的各个成员变量,成员变量的内存地址如下

  • isa的内存地址是首地址,前面已经探究了
  • superclass的内存地址是 首地址+0x8
  • cache的内存地址是 首地址+0x10
  • bits的内存地址是 首地址+0x20

获取bits

今天主要对bits进行探究。

image.png

bits.data()应该存储数据,data()的类型是class_rw_t*,源码分析下class_rw_t类型,源码比较长,需要多看源码学会一种思想,这边是找方法定位关键代码

image.png

class_rw_t是结构体类型,提供了获取方法列表属性列表协议列表的方法。通过实例来验证下方法属性变量是不是在class_rw_t中,在XHPerson类中添加属性和方法 以及成员变量

image.png

进行lldb调试

属性探究

image.png 类中声明的属性namehobby存储在属性列表preperty_list_t里,p $7.get(0)p $7.get(1)获取相应的属性。

对象方法探究

image.png

image.png

  • 对象方法存储在XHPerson类中的方法列表method_list_t里。
  • 类方法没有在XHPerson的方法列表method_list_t里。
  • p $7.get(x)在方法列表中获取不到具体的值,因为method_t中进行了处理,通过big()获取变量。

类方法探究

对象的方法是存储在中,那么类方法可能存储在元类中。按照这个思路探究下

image.png

  • 类方法存储在元类中的方法列表里。

总结

中有isasuperclasscachebits 成员变量,在对bits 探究过程中发现bits存储了属性列表、方法列表、成员变量列表、协议列表等,类方法存储在元类里面。

后续还要对类进行分析,敬请期待。