类的底层探究(isa,superclass)

429 阅读2分钟

objc_object & objc_class的区别

  1. objc_object对应对象,是对象在底层的的数据结构
  2. objc_class对应,是在底层的数据结构
  3. idobjc_object指针别名
typedef struct objc_object *id;`
  1. classobjc_class指针别名
typedef struct objc_class *Class;
  1. objc_class继承于objc_object,万物皆对象
struct objc_class : objc_object {

isa

源码分析

探究objc_object,我们知道objc_object在内存中由isa_t isa成员变量构成。 查看isa_t的源码

union isa_t {
    uintptr_t bits;

private:
    Class cls;

public:
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};

  1. isa是个联合体(共用体)
  2. isa在新版内存优化后,通过位域的方式,由原来64位单纯作为类指针变为剩余空间可以存放其它信息。例如在x86架构下,中间44位是存放类指针地址,其它为存放了关联对象信息、弱引用、散列表等信息,更大限度的节省内存空间
  3. 其中ISA_MASK作为掩码,在ISA_BITFIELD位域的冗余信息中过滤出shiftcls,类指针的值.
define ISA_MASK        0x00007ffffffffff8ULL
define ISA_BITFIELD
      uintptr_t nonpointer        : 1;
      uintptr_t has_assoc         : 1; 
      uintptr_t has_cxx_dtor      : 1;
      uintptr_t shiftcls          : 44; 
      uintptr_t magic             : 6; 
      uintptr_t weakly_referenced : 1;
      uintptr_t unused            : 1;
      uintptr_t has_sidetable_rc  : 1;
      uintptr_t extra_rc          : 8

我们可以看到他们的流程

graph TD
对象对应objc_object --> objc_object由isa构成
objc_object由isa构成 --> isa指向类objc_Class
isa指向类objc_Class --> objc_Class继承于objc_object
objc_Class继承于objc_object --> objc_object由isa构成

打印isa内存信息

对象的isa指向类,那么类的isa指向什么?

实例中我们探究XXPerson的内存情况

XXPerson *p = [XXPerson alloc];
NSLog(@"%@",p);
  1. p 打印出对象的内存地址
  2. x/4gx 计算出当前对象指向的内存地址相邻4个单位的内存地址
  3. p/x isa地址 & ISA_MASK 计算出shiftcls(类对象)的指针地址
  4. po 打印出类对象描述信息
  5. 根据步骤3计算出的类对象地址,重复步骤2-4

image.png

  • p->isa == p.class == 0x0000000100008270 == XXPerson
  • p.class->isa == p.class.class == 0x0000000100008248 == XXPerson
  • (p.class->isa)->isa == p.class.class.class == 0x00007fff88c65ca0 == NSObject
  • ((p.class->isa)->isa)->isa == p.class.class.class.class == 0x00007fff88c65ca0 == NSObject

元类(metal class)

类的isa指向的是元类

我们可以看到p.class 和 p.class.class的打印内存地址不同,但通过po打印出来的描述信息相同,都为XXPerson

打开MacOView,在符号表中搜索XXPerson,找到了_OBJC_METACLASS_$_XXPerson, 这里的类型就对应了p.class->isa所指向的类型,即元类

image.png

根元类 (root metal class)

元类的isa指向的是根元类

根元类打印出来的描述信息为NSObject,且再往后isa一直指向0x00007fff88c65ca0这个地址,po打印的描述信息为NSObject

image.png

  • 根元类NSObject的元类指向的地址0x00007fff88c65ca0一致
  • 根元类NSObject类指向的地址0x00007fff88c65cc8不同
  • 根元类isa指向的还是根元类

总结

非NSObject

  • 实例的isa-->
  • 类的isa-->元类
  • 元类的isa-->根元类
  • 根元类的isa-->自己 NSObject
  • NSObject-->NSObject的类
  • NSObject的类-->NSObject的元类
  • NSObject的元类-->自己
  • NSObject的元类根元类

image.png

superclass

元类存在继承关系

Class pMetaClass = object_getClass(LGPerson.class);
Class psuperClass = class_getSuperclass(pMetaClass);
NSLog(@"%@ - %p",psuperClass,psuperClass);

Class tMetaClass = object_getClass(LGTeacher.class);
Class tsuperClass = class_getSuperclass(tMetaClass);
NSLog(@"%@ - %p",tsuperClass,tsuperClass);

打印结果

image.png

LGPerson是LGTeacher的父类,从上述打印结果可以看到,元类存在继承关系,元类的父类是父类的元类。

NSObject的父类是nil

Class nsuperClass = class_getSuperclass(NSObject.class);
NSLog(@"%@ - %p",nsuperClass,nsuperClass);

打印结果

image.png

NSObject元类的父类是NSObject类

上代码

Class rnsuperClass = class_getSuperclass(metaClass);
NSLog(@"%@ - %p",rnsuperClass,rnsuperClass);

打印结果

image.png

这里和上面打印出来的NSObject类的地址相同,从另一的角度理解,元类也是类,所以他们的继承关系图中,最终指向的还是NSObject类万物皆NSObject

类、元类继承关系的图解

image.png

苹果官方文档isa走位图和类,元类的继承图

isa流程图.png

class_data_bits_t bits

首先,我们查看元类的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_data_bits_t bits地址的定位

方法,成员变量,协议之类的信息存放在bits,我们可以通过地址偏移的方式定位类对象中的bits 其中我们确定的是类对象继承自objc_object所以对应的第一个成员变量isa占了8个字节superclass也占了8个字节,但是不知道cache_t cache所占的内存空间大小。查看源码,通过去掉方法,全局变量等不占对象内存空间的代码,提炼出一下部分的代码

private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask; // 8
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask; // 4
#if __LP64__
            uint16_t                   _flags; // 2 
#endif
            uint16_t                   _occupied;// 2
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache; // 8
    };

由于结构体的下面那那部分成员时联合体,共享内存,通过简单的计算得知, cache_t cache所占的内存8 + 8 为16个字节,所以,加上上面的isa和superclass取class_data_bits_t bits的地址需要偏移32个字节

成员变量信息

按照如下步骤打印内存

  1. x/4gx LGPerson.class,得出内存首地址0x100008380
  2. p/x 0x100008380 + 0x20,得出偏移后的内存0x00000001000083a0
  3. 强转类型p (class_data_bits_t *)0x00000001000083a0,得出lldb中变量$97
  4. 通过p $97->data(),获取class_data_bits_t bits中的数据,得到(class_rw_t *) $98 = 0x0000000101233ca0
  5. p $98->properties() 获取类成员信息
(const property_array_t) $100 = {
  list_array_tt<property_t, property_list_t, RawPtr> = {
     = {
      list = {
        ptr = 0x0000000100008260
      }
      arrayAndFlag = 4295000672
    }
  }
}
  1. p $100.list
  2. p $101.ptr
  3. p *$113
p *$113
(property_list_t) $114 = {
  entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 2)
}
  1. 打印结果是个数组,所以通过get方法,即p $114.get(0)打印内存情况
(property_t) $115 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")

方法

  1. x/4gx LGPerson.class,打印类对象内存地址
0x100008380: 0x00000001000083a8 0x000000010036a140
0x100008390: 0x00000001012340f0 0x0002802800000003
  1. 输入p/x 0x100008380 + 0x20 获取class_data_bits_t bits的32个字节的偏移地址
$117 = 0x00000001000083a0
  1. 输入p (class_data_bits_t *)0x00000001000083a0,类型强转为class_data_bits_t指针
(class_data_bits_t *) $118 = 0x00000001000083a0
  1. 输入p $119->methods(),获取方法信息
(const method_array_t) $120 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x0000000100008160
      }
      arrayAndFlag = 4295000416
    }
  }
}
  1. 输入p $120.list
(const method_list_t_authed_ptr<method_list_t>) $121 = {
  ptr = 0x0000000100008160
}
  1. p $121.ptr
(method_list_t *const) $122 = 0x0000000100008160
  1. 输入p *$122
(method_list_t) $123 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 6)
}
  1. 输入p $123.get(0).big(),其中get(0)为获取数组的method_t的第一个元素,big()打印方法信息
(method_t::big) $125 = {
  name = "sayNB"
  types = 0x0000000100003f77 "v16@0:8"
  imp = 0x0000000100003d40 (KCObjcBuild`-[LGPerson sayNB])
}