类结构探究(二)-- bits结构探究

在上一篇文章中我们已经探究了isa和superclass的指向问题,本文将通过lldb调试,探索objc_class中bit的存储信息。

探索原理

我们先看下objc_class的结构。

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits; 
    ...
}

我们知道objc_class的首地址是由isa开始,而之后依次是superclass、cache、bits,如果我们要访问探究bits的内存,那么只需要将objc_class的地址加上isa、superclass和cache占用内存大小的和,就可以访问到bits的空间。

isa和superclass本质都是指针类型,占用8个字节,而cache_t占用16个字节的空间,所以objc_class地址偏移32个字节的大小,就可以得到bits的地址。

关于cache_t的大小可以通过查看它的结构得知
cache_t中决定大小只以下几个成员(其余静态变量不影响大小)

struct cache_t {
    ... 
    explicit_atomic _buckets;
    explicit_atomic _mask;
    ...
    uint16_t _flags;
    ...
    uint16_t _occupied;
    ...
}

_buckets是指针类型,占用8个字节,mask_t实际是uint32_t类型,占用4个字节,_flags和_occupied均是uint16_t,占用2个字节,所以cache_t占用8+4+2+2=16个字节

本文探究需要借助可以编译的objc4-7.8.1源码

探究准备

与上次一样,本文继续采用Animal和Cat类来探究,不同的是,我们在Cat中添加属性和方法。

@interface Cat : Animal {
    NSString *name;
}

@property(nonatomic, strong) NSString *ownName;

@end

@implementation Cat

- (void)eat {
}

+ (void)run {
}
@end

在main方法中打上断点,开始探究

探究过程

进入断点后,执行p/x Cat.class:

(lldb) p/x Cat.class
(Class) $0 = 0x0000000100002268 Cat

给Cat的地址加上偏移量0x20,p/x查看

(lldb) p/x 0x0000000100002288
(long) $1 = 0x0000000100002288

转化为class_data_bits_t

(lldb) p (class_data_bits_t *)$1
(class_data_bits_t *) $2 = 0x0000000100002288

通过data()获取bits

(lldb) p $2->data()
(class_rw_t *) $3 = 0x0000000100862150

查看class_rw_t的结构,可以看到它有romethodspropertiesprotocols几个重要方法,我们通过lldb一步步探究。

(lldb) p/x *$3
(class_rw_t) $4 = {
  flags = 0x80080000
  witness = 0x0001
  ro_or_rw_ext = {
    std::__1::atomic = 0x0000000100002088
  }
  firstSubclass = nil
  nextSiblingClass = nil
}

properties

首先查看properties保存了哪些内容。

(lldb) p $4.properties()
(const property_array_t) $5 = {
  list_array_tt = {
     = {
      list = 0x0000000100002180
      arrayAndFlag = 4294975872
    }
  }

继续查看list

(lldb) p $5.list
(property_list_t *const) $6 = 0x0000000100002180

(lldb) p *$6
(property_list_t) $7 = {
  entsize_list_tt = {
    entsizeAndFlags = 16
    count = 1
    first = (name = "ownName", attributes = "T@\"NSString\",&,N,V_ownName")
  }
}

可以看到properties数量只有一个ownName,而成员变量name并不在其中,所以我们可以得出结论,properties保存属性(property)。

ro

同样查看ro的内容

(lldb) p $4.ro()
(const class_ro_t *) $8 = 0x0000000100002088

(lldb) p *$8
(const class_ro_t) $9 = {
  flags = 388
  instanceStart = 8
  instanceSize = 24
  reserved = 0
  ivarLayout = 0x0000000100000f18 "\x02"
  name = 0x0000000100000f14 "Cat"
  baseMethodList = 0x00000001000020d0
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000100002138
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x0000000100002180
  _swiftMetadataInitializer_NEVER_USE = {}
}

查看ivars

(lldb) p $9.ivars 
(const ivar_list_t *const) $10 = 0x0000000100002138

(lldb) p *$10
(const ivar_list_t) $11 = {
  entsize_list_tt = {
    entsizeAndFlags = 32
    count = 2
    first = {
      offset = 0x0000000100002238
      name = 0x0000000100000f25 "name"
      type = 0x0000000100000f61 "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
  }
}

可以看到Cat的ivars有两个,通过索引查看

(lldb) p $11.get(0)
(ivar_t) $12 = {
  offset = 0x0000000100002238
  name = 0x0000000100000f25 "name"
  type = 0x0000000100000f61 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $11.get(1)
(ivar_t) $13 = {
  offset = 0x0000000100002230
  name = 0x0000000100000f2a "_ownName"
  type = 0x0000000100000f61 "@\"NSString\""
  alignment_raw = 3
  size = 8
}

可以看,ivars中不仅存放了成员变量name,而且经过@property修饰的属性也会在变量名前加上"_"保存在ivar中。

methods

查看methods的内容

(lldb) p $4.methods()
(const method_array_t) $14 = {
  list_array_tt = {
     = {
      list = 0x00000001000020d0
      arrayAndFlag = 4294975696
    }
  }
}

(lldb) p $14.list
(method_list_t *const) $15 = 0x00000001000020d0

(lldb) p *$15
(method_list_t) $16 = {
  entsize_list_tt = {
    entsizeAndFlags = 26
    count = 4
    first = {
      name = "eat"
      types = 0x0000000100000f59 "v16@0:8"
      imp = 0x0000000100000e10 (KCObjc`-[Cat eat])
    }
  }
}

可以看到methods中保存了4个方法信息,可以通过索引查看具体内容

(lldb) p $16.get(0)
(method_t) $17 = {
  name = "eat"
  types = 0x0000000100000f59 "v16@0:8"
  imp = 0x0000000100000e10 (KCObjc`-[Cat eat])
}

(lldb) p $16.get(1)
(method_t) $18 = {
  name = "ownName"
  types = 0x0000000100000f6d "@16@0:8"
  imp = 0x0000000100000e20 (KCObjc`-[Cat ownName])
}

(lldb) p $16.get(2)
(method_t) $19 = {
  name = "setOwnName:"
  types = 0x0000000100000f75 "v24@0:8@16"
  imp = 0x0000000100000e40 (KCObjc`-[Cat setOwnName:])
}

(lldb) p $16.get(3) 
(method_t) $20 = {
  name = ".cxx_destruct"
  types = 0x0000000100000f59 "v16@0:8"
  imp = 0x0000000100000e80 (KCObjc`-[Cat .cxx_destruct])
}

可以看到methods中保存了实例方法eat,ownName的set和get方法(编译器生成的),还有一个析构方法,但是,类方法run却不在其中。

实际上,对象的实例方法保存在类对象,而属于类对象的类方法,是保存在类对象的类--元类中的,我们可以通过同样的过程验证。

验证类方法的保存位置

我们通过类的isa找到元类,用同样的指针偏移找到类方法

// 查看Cat类信息
(lldb) x/4gx Cat.class
0x100002268: 0x0000000100002240 0x00000001000022b8
0x100002278: 0x00000001008621f0 0x0001802400000003
// 获取元类地址
(lldb) p/x 0x0000000100002240 & 0x00007ffffffffff8ULL
(unsigned long long) $22 = 0x0000000100002240
(lldb) p/x 0x0000000100002260
(long) $23 = 0x0000000100002260
// 元类地址加上0x20得到class_data_bits_t
(lldb) p (class_data_bits_t *)$23
(class_data_bits_t *) $24 = 0x0000000100002260
// 获取class_rw_t
(lldb) p $24->data()
(class_rw_t *) $25 = 0x00000001008161a0
(lldb) p *$25
(class_rw_t) $26 = {
  flags = 2684878849
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic = 4294975520
  }
  firstSubclass = nil
  nextSiblingClass = nil
}
// 获取methods
(lldb) p $26.methods()
(const method_array_t) $27 = {
  list_array_tt = {
     = {
      list = 0x0000000100002068
      arrayAndFlag = 4294975592
    }
  }
}
(lldb) p $27.list
(method_list_t *const) $28 = 0x0000000100002068
// 打印method信息
(lldb) p *$28
(method_list_t) $29 = {
  entsize_list_tt = {
    entsizeAndFlags = 26
    count = 1
    first = {
      name = "run"
      types = 0x0000000100000f59 "v16@0:8"
      imp = 0x0000000100000e00 (KCObjc`+[Cat run])
    }
  }
}

猜想得证。

总结

本文主要对objc_class中bits保存信息的一个探究,结论如下

  1. 类对象的bits保存了类的属性、成员变量、方法、协议等信息
  2. 实例方法保存在类对象中,具体在class_rw_t的methods中
  3. 元类对象的bits保存类方法,具体在class_rw_t的methods中
  4. property修饰的属性会生成set和get方法,属性信息保存在properties中,通过会生成一个“_属性名”保存在ro的ivars中
  5. 成员变量保存在ro的ivars中

你可能感兴趣的:(类结构探究(二)-- bits结构探究)