OC底层探索08-基于objc4-781类结构分析

作者: Henry________ | 来源:发表于2020-09-23 17:34 被阅读0次

OC底层探索06-isa本身藏了多少信息你知道吗?分析了isa

在平时的开发中应该都接触或者使用过缓存的技术,目的就是提高执行效率,用空间换取时间。当然apple在这方面一定也有其特别的地方。

// 再熟悉一下objc_class的结构
struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             
    class_data_bits_t bits;
    ...
}

本文中会注重介绍objc_object中的cache

提到缓存那么cache面缓存的是什么呢:属性还方法?其实很好猜测,平时开发中使用最多的就是方法,因为只有方法才会引起变化。下面会通过两种方式进行验证这个猜测。

首先了解一下这3个宏定义

  • define CACHE_MASK_STORAGE_OUTLINED //代表当前环境:模拟器、macos
  • define CACHE_MASK_STORAGE_HIGH_16 2 //代表当前环境:64位真机
  • define CACHE_MASK_STORAGE_LOW_4 3 //代表当前环境:低于64位真机

通过cache_t了解cache到底缓存了什么?

struct cache_t {
    //不同设备环境数据结构不同
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    explicit_atomic<struct bucket_t *> _buckets;
    explicit_atomic<mask_t> _mask;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    explicit_atomic<uintptr_t> _maskAndBuckets;
    mask_t _mask_unused;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    explicit_atomic<uintptr_t> _maskAndBuckets;
    mask_t _mask_unused;
    
    uint16_t _flags;
    uint16_t _occupied;
    ...
}

struct bucket_t {
#if __arm64__
    explicit_atomic<uintptr_t> _imp;    //方法指针
    explicit_atomic<SEL> _sel;  //方法标示
#else
    explicit_atomic<SEL> _sel;
    explicit_atomic<uintptr_t> _imp;
#endif
}
  • cache根据当前环境分为3个版本.

  • explicit_atomic<X>:类似于swift中的泛型。将当前类型设置为原子性,也就是将其设置为线程安全。(毕竟在多线程中调用方法的场景太多了)

  • mask_t _mask_unused: 根据命名当前参数还未进行使用

  • 主要信息存在:_buckets | _maskAndBuckets

cache_t的结构图

下方是最新的objc-781的 objc_class结构

struct objc_object {    //所有对象的模板类
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

struct objc_class : objc_object {
    // Class ISA;   //偏移量:8位
    Class superclass;   //偏移量:8位
    cache_t cache;  //偏移量:8 + 4 + 2 + 2 = 16位
    class_data_bits_t bits;  
    class_rw_t *data() const {
        return bits.data();
    }
    ...
}

内存偏移

这lldb中我们无法直接访问objc_class中的信息,只能通过指针访问的方式来进行验证,所以这里需要用到内存偏移

int c[4] = {1, 2, 3, 4};
int *d = c;
NSLog(@"%p -- %p - -- %p --- %p", &c, &c[0], &c[1],&c[2]);
NSLog(@"%p -- %p - %p", d, d+1, d+2);
企业微信截图_7568a4b2-695a-48d0-97ee-303210e61466.png
  • int是4位我们可以直接对地址进行+4来访问数组的下一个成员。

类的内部信息

手动计算objc_class源码中class_data_bits_t bits;的偏移量:32位,(在加上结构体:class_data_bits_t的8位,正好40位。印证了上文中的猜测)

1.拿到objc_class中的class_rw_t

(lldb) p/x HRTest.class
(Class) $0 = 0x00000001000033c0 HRTest  //类头地址
(lldb) p (class_data_bits_t *)0x00000001000033d0    //类地址偏移0x20
(class_data_bits_t *) $1 = 0x00000001000033d0
(lldb) p $1->data()
(class_rw_t *) $2 = 0x00000001000033e8  //数据都存放在class_rw_t里

2.查看class_rw_t源码

注:此处只放出和目标有关的信息

const class_ro_t *ro()  

const method_array_t methods()  //对象方法列表
    
const property_array_t properties()  //属性

const protocol_array_t protocols()  //类的协议
类中方法列表
(class_rw_t *) $2 = 0x00000001012b46c0  //class_rw_t
(lldb) p $2->methods()  //方法list
(const method_array_t) $3 = {
  list_array_tt<method_t, method_list_t> = {
     = {
      list = 0x0000000100002108
      arrayAndFlag = 4294975752
    }
  }
}
(lldb) p $3.list
(method_list_t *const) $4 = 0x0000000100002108
(lldb) p *$4
(method_list_t) $5 = {
    ...
}
(lldb) p $5.get(0)
(method_t) $6 = {
  name = "say222"   //对象方法say222
  types = 0x0000000100000f73 "v16@0:8"
  imp = 0x0000000100000d50 (HRTest`-[HRTest say222])
}
(lldb) p $5.get(1)
(method_t) $7 = {
  name = ".cxx_destruct"
  types = 0x0000000100000f73 "v16@0:8"
  imp = 0x0000000100000d60 (HRTest`-[HRTest .cxx_destruct])
}
(lldb) p $5.get(2)
(method_t) $8 = {
  name = "name"     //自动生成属性的get方法
  types = 0x0000000100000f87 "@16@0:8"
  imp = 0x0000000100000da0 (HRTest`-[HRTest name])
}
(lldb) p $5.get(3)
(method_t) $9 = {
  name = "setName:" //自动生成属性的set方法
  types = 0x0000000100000f8f "v24@0:8@16"
  imp = 0x0000000100000dd0 (HRTest`-[HRTest setName:])
}
(lldb) p $5.get(4)
Assertion failed: (i < count)...    //数组越界了

找到了对象方法,属性的get,set方法,唯独没有知道类方法。

元类中方法列表
(lldb) x/gx HRTest.class
0x100002218: 0x00000001000021f0 //类的元类
(lldb) p (class_data_bits_t *)0x0000000100002210
(class_data_bits_t *) $1 = 0x0000000100002210
(lldb) p $1->data()
(class_rw_t *) $2 = 0x00000001015338d0
(lldb) p $2->methods()
(const method_array_t) $3 = {
  list_array_tt<method_t, method_list_t> = {
     = {
      list = 0x00000001000020a0
      arrayAndFlag = 4294975648
    }
  }
}
(lldb) p $3.list
(method_list_t *const) $4 = 0x00000001000020a0
(lldb) p *$4
(method_list_t) $5 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 1
    first = {
      name = "say666"
      types = 0x0000000100000f73 "v16@0:8"
      imp = 0x0000000100000d40 (HRTest`+[HRTest say666])
    }
  }
}
  • 在元类中找到了类方法say666
属性

查看方式与方法相同:properties()

成员变量
(class_rw_t *) $2 = 0x0000000101822660  //class_rw_t
(lldb) p $2->ro()   //ro
(const class_ro_t *) $3 = 0x00000001000020c0
(lldb) p $3->ivars   //成员变量
(const ivar_list_t *const) $4 = 0x0000000100002170
(lldb) p *$4    //取地址
(const ivar_list_t) $5 = {
    ...
}
(lldb) p $5.get(0)
(ivar_t) $7 = {     //定义的成员变量
  offset = 0x00000001000021e0
  name = 0x0000000100000ed2 "HRTestName"
  type = 0x0000000100000f7b "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $5.get(1)
(ivar_t) $8 = {     //name属性自定生成的:_name成员变量
  offset = 0x00000001000021e8
  name = 0x0000000100000edd "_name"
  type = 0x0000000100000f7b "@\"NSString\""
  alignment_raw = 3
  size = 8
}
  • 成员变量是在class_rw_t->ro()->ivars
  • 成员变量不单有开发者创建的,还有属性也会自动创建一个_属性名

iOS14之后类的结构发生变化

  • 将需要在运行时改变的数据单独放在class_rw_ext_t这个结构中。超过90%的普通类都不会动态进行修改方法、协议、属性,所以大多数只需要class_rw_t这个结构就完整了。
  • 90%的类省了四个字段: 4*8 = 32个字节

相关文章

网友评论

    本文标题:OC底层探索08-基于objc4-781类结构分析

    本文链接:https://www.haomeiwen.com/subject/btsvyktx.html