前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >OC底层探索08-基于objc4-781类结构分析OC底层探索08-基于objc4-781类结构分析

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

作者头像
用户8893176
发布2021-08-09 11:26:07
2930
发布2021-08-09 11:26:07
举报
文章被收录于专栏:小黑娃Henry

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

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

代码语言:javascript
复制
// 再熟悉一下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到底缓存了什么?
代码语言:javascript
复制
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结构
代码语言:javascript
复制
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中的信息,只能通过指针访问的方式来进行验证,所以这里需要用到内存偏移

代码语言:javascript
复制
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
代码语言:javascript
复制
(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源码

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

代码语言:javascript
复制
const class_ro_t *ro()  

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

const protocol_array_t protocols()  //类的协议
类中方法列表
代码语言:javascript
复制
(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方法,唯独没有知道类方法。

元类中方法列表
代码语言:javascript
复制
(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()

成员变量
代码语言:javascript
复制
(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个字节

补充 objc4-818.2

在objc4-781中method_t结构是这样

代码语言:javascript
复制
struct method_t {
    SEL name;
    const char *types;
    MethodListIMP imp;

在objc4-818.2版本中method_t结构在发生了变化,增加了一个结构体嵌套:

代码语言:javascript
复制
struct method_t {
    ...
    struct big {
        SEL name;
        const char *types;
        MethodListIMP imp;
    };

所以在lldb调试的时候,会有一点变化

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020/9/23 下,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 本文中会注重介绍objc_object中的cache
    • 首先了解一下这3个宏定义
      • 通过cache_t了解cache到底缓存了什么?
      • cache_t的结构图
        • 下方是最新的objc-781的 objc_class结构
        • 内存偏移
        • 类的内部信息
          • 1.拿到objc_class中的class_rw_t
            • 2.查看class_rw_t源码
              • 类中方法列表
              • 元类中方法列表
              • 属性
              • 成员变量
            • iOS14之后类的结构发生变化
            • 补充 objc4-818.2
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档