iOS底层原理 04 : isa指向&类的结构分析

前言:
1.我们前面已经对isa的结构进行了分析,但整个isa指向过程是什么呢?
2.我们经常看到的那张经典的isa走位图又该如何理解呢?
3.前面我们已经知道实例对象的isa指针指向了存储类信息的类对象,那么这个存储类信息的类的结构又是怎样的呢?

使用LLDB探索isa走位

  • 首先定义LGHPerson,LGHTeacher类,并创建实例对象,先在main函数里面的objc打个断点。


    iOS底层原理 04 : isa指向&类的结构分析_第1张图片
    准备.png
  • 然后使用lldb, 查看p对象的内存

(lldb) po p


(lldb) x/4gx  0x1006d1dc0
0x1006d1dc0: 0x001d800100002181 0x0000000000000000
0x1006d1dd0: 0x726573554b575b2d 0x7320747069726353
  • 获取isa的shiftcls的地址
(lldb) p/x  0x001d800100002181 & 0x00007ffffffffff8ULL
(unsigned long long) $3 = 0x0000000100002180
  • 读取该段地址,与x LGHPerson.class比较,发现一致,说明p.isa --> 存储类信息的类。
(lldb) x  0x0000000100002180
0x100002180: 58 21 00 00 01 00 00 00 40 11 3f 00 01 00 00 00  X!......@.?.....
0x100002190: 30 57 6d 00 01 00 00 00 07 00 00 00 10 80 04 00  0Wm.............
(lldb) x LGHPerson.class
0x100002180: 58 21 00 00 01 00 00 00 40 11 3f 00 01 00 00 00  X!......@.?.....
0x100002190: 30 57 6d 00 01 00 00 00 07 00 00 00 10 80 04 00  0Wm.............
(lldb) x  object_getClass(p)
0x100002180: 58 21 00 00 01 00 00 00 40 11 3f 00 01 00 00 00  X!......@.?.....
0x100002190: 30 57 6d 00 01 00 00 00 07 00 00 00 10 80 04 00  0Wm.............
  • 接下来我们x/4gx 打印存储类信息的类对象的内存
(lldb) x/4gx 0x0000000100002180
0x100002180: 0x0000000100002158 0x00000001003f1140
0x100002190: 0x00000001006d5730 0x0004801000000007
  • 接下来,我们看看类对象得isa的shiftcls
(lldb) p/x 0x0000000100002158 & 0x00007ffffffffff8ULL
(unsigned long long) $8 = 0x0000000100002158
  • 读取类对象得isa的shiftcls存储的内存,其实就是元类,系统会自动帮我生成。所以类对象的isa-->元类
(lldb) x/4gx 0x0000000100002158
0x100002158: 0x00000001003f10f0 0x00000001003f10f0
0x100002168: 0x0000000101208fc0 0x0001e03100000007
  • 我们继续那元类的isa,获取shiftcls
(lldb) p/x 0x00000001003f10f0 & 0x00007ffffffffff8ULL
(unsigned long long) $13 = 0x00000001003f10f0
  • 读取地址 ,并与存储NSObject类信息的类对象地址对比,发现一致,说明元类的isa-->存储NSObject类信息的类(根元类)
(lldb) x/4gx 0x00000001003f10f0
0x1003f10f0: 0x00000001003f10f0 0x00000001003f1140
0x1003f1100: 0x000000010070a2f0 0x0005e03100000007
(lldb) x/4gx NSObject.class
0x1003f1140: 0x00000001003f10f0 0x0000000000000000
0x1003f1150: 0x000000010110b1c0 0x0001801000000003
  • 我们继续探索根元类的isa指向,发现指向根元类自己
(lldb) p/x  0x00000001003f10f0 & 0x00007ffffffffff8ULL
(unsigned long long) $16 = 0x00000001003f10f0
(lldb) x/4gx  0x00000001003f10f0
0x1003f10f0: 0x00000001003f10f0 0x00000001003f1140
0x1003f1100: 0x000000010070a2f0 0x0005e03100000007

总结:
1.实例对象的isa -->类对象
2.类对象的isa指向元类对象
3.元类对象的isa指向根元类对象
4.根元类对象的isa指向自身

iOS底层原理 04 : isa指向&类的结构分析_第2张图片
LGHPerson的isa走位图.png

同理LGHTeacher的走位图


iOS底层原理 04 : isa指向&类的结构分析_第3张图片
LGHTeacher的走位图.png
iOS底层原理 04 : isa指向&类的结构分析_第4张图片
NSObject的走位图.png
iOS底层原理 04 : isa指向&类的结构分析_第5张图片
继承与isa走位图.png

存储类信息的类结构

我们看到objc_class定义,但我们发现OBJC2_UNAVAILABLE,这是已经废弃掉的结构。

// 旧的类结构:
struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;

我们继续寻找objc_class的新结构,我们可以看到objc_class的新版定义(objc4-781版本)如下,有以下几个属性

// 在最新的objc源码里面:
struct objc_class : objc_object {
    // Class ISA; //8字节
    Class superclass; //Class 类型 8字节
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags    
    //....方法部分省略,未贴出
}
  • isa属性:继承自objc_object的isa,占 8字节

  • superclas 属性:Class类型Class是由objc_object定义的,是一个指针,占8字节

  • cache属性:简单从类型class_data_bits_t目前无法得知,而class_data_bits_t是一个结构体类型,结构体的内存大小需要根据内部的属性来确定,而结构体指针才是8字节

  • bits属性:只有首地址经过上面3个属性的内存大小总和的平移,才能获取到bits

计算 cache_t 结构体的内存大小

struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    explicit_atomic _buckets; // 是一个结构体指针类型,占8字节
    explicit_atomic _mask; //是mask_t 类型,而 mask_t 是 unsigned int 的别名,占4字节
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    explicit_atomic _maskAndBuckets; //是指针,占8字节
    mask_t _mask_unused; //是mask_t 类型,而 mask_t 是 uint32_t 类型定义的别名,占4字节
    
#if __LP64__
    uint16_t _flags;  //是uint16_t类型,uint16_t是 unsigned short 的别名,占 2个字节
#endif
    uint16_t _occupied; //是uint16_t类型,uint16_t是 unsigned short 的别名,占 2个字节
  • 计算前两个属性的内存大小,有以下两种情况,最后的内存大小总和都是12字节

    • 【情况一】if流程
      • buckets 类型是struct bucket_t *,是结构体指针类型,占8字节
      • maskmask_t 类型,而 mask_t 是 unsigned int的别名,占4字节
    • 【情况二】elseif流程
  • _maskAndBuckets 是uintptr_t类型,它是一个指针,占8字节

  • _mask_unused 是mask_t 类型,而 mask_t 是 uint32_t 类型定义的别名,占4字节

  • _flags 是uint16_t类型,uint16_t是 unsigned short 的别名,占2个字节

  • _occupied 是uint16_t类型,uint16_t是 unsigned short 的别名,占 2个字节

总结:所以最后计算出cache类的内存大小 = 12 + 2 + 2 = 16字节

获取bits

所以有上述计算可知,想要获取bits的中的内容,只需通过类的首地址平移32字节即可。如下:($1+32)

(lldb) p/x LGHPerson.class
(Class) $0 = 0x00000001000022a0 LGHPerson
(lldb) p/x 0x00000001000022a0
(long) $1 = 0x00000001000022a0
(lldb) p/x (class_data_bits_t *)($1+32)
(class_data_bits_t *) $2 = 0x00000001000022c0

接下来获取properties(),得到里面存储了name属性

(lldb) p $4.properties()
(const property_array_t) $12 = {
  list_array_tt = {
     = {
      list = 0x00000001000021a8
      arrayAndFlag = 4294975912
    }
  }
}
(lldb) p $12.list
(property_list_t *const) $13 = 0x00000001000021a8
(lldb) p *$13
(property_list_t) $14 = {
  entsize_list_tt = {
    entsizeAndFlags = 16
    count = 1
    first = (name = "name", attributes = "T@\"NSString\",&,N,V_name")
  }
}
(lldb) p $14.get(0)
(property_t) $15 = (name = "name", attributes = "T@\"NSString\",&,N,V_name")

数组越界,说明只存储了name

(lldb) p $14.get(1)
Assertion failed: (i < count), function get, file /Users/xiaofeiguan/Desktop/04-isa指向&类初探/03-自己补充/可编译objc源码/runtime/objc-runtime-new.h, line 438.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.

类信息里面的properties()里面只存储属性,没有存储成员变量

接下来获取methods() ,得到-sayHello -setName -name 这个实例方法

(lldb) p $4.methods()
(const method_array_t) $5 = {
  list_array_tt = {
     = {
      list = 0x00000001000020d8
      arrayAndFlag = 4294975704
    }
  }
}
(lldb) p $5.list
(method_list_t *const) $6 = 0x00000001000020d8
(lldb) p *$6
(method_list_t) $7 = {
  entsize_list_tt = {
    entsizeAndFlags = 26
    count = 4
    first = {
      name = "sayHello"
      types = 0x0000000100000f42 "v16@0:8"
      imp = 0x0000000100000d70 (KCObjc`-[LGHPerson sayHello] at main.m:21)
    }
  }
}
(lldb) p $7.get(0)
(method_t) $8 = {
  name = "sayHello"
  types = 0x0000000100000f42 "v16@0:8"
  imp = 0x0000000100000d70 (KCObjc`-[LGHPerson sayHello] at main.m:21)
}
(lldb) p $7.get(1)
(method_t) $9 = {
  name = ".cxx_destruct"
  types = 0x0000000100000f42 "v16@0:8"
  imp = 0x0000000100000d80 (KCObjc`-[LGHPerson .cxx_destruct] at main.m:20)
}
(lldb) p $7.get(2)
(method_t) $10 = {
  name = "name"
  types = 0x0000000100000f58 "@16@0:8"
  imp = 0x0000000100000dc0 (KCObjc`-[LGHPerson name] at main.m:15)
}
(lldb) p $7.get(3)
(method_t) $11 = {
  name = "setName:"
  types = 0x0000000100000f60 "v24@0:8@16"
  imp = 0x0000000100000de0 (KCObjc`-[LGHPerson setName:] at main.m:15)
}

数组越界,说明类信息里面只存储实例方法不存储类方法

(lldb) p $7.get(4)
Assertion failed: (i < count), function get, file /Users/xiaofeiguan/Desktop/04-isa指向&类初探/03-自己补充/可编译objc源码/runtime/objc-runtime-new.h, line 438.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.

你可能感兴趣的:(iOS底层原理 04 : isa指向&类的结构分析)