美文网首页
02--对象本质04--类的结构

02--对象本质04--类的结构

作者: 修_远 | 来源:发表于2020-07-17 12:15 被阅读0次

    [TOC]

    1. 怎么读到cache_t属性


      image

    四个属性

    在源码中关于类objc_class的结构体代码, 总共有200多行, 而我们最关注的只有上述四个属性

    类结构源码
    1. // Class ISA;: 8字节

      • isa属性是继承与父类的属性, 所以是在属性的第一个位置, 表示isa指针
      • 指针占8个字节
    2. Class superclass;: 8字节

      • 除了isa属性之外, 父类指针就是第二个位置, 表示父类的isa指针
      • 指针占8个字节
    3. cache_t cache;: 16字节

      struct cache_t {
      struct bucket_t *_buckets;  // 8
      mask_t _mask;               // 4
      mask_t _occupied;           // 4
      

      我们可以先看看cache_t的结构, 根据内存对齐的规则, 可以计算出这个结构体的大小为 16字节

      • cache_t结构体
      • 16字节
    4. class_data_bits_t bits;: 所有的数据存储位置, 重点研究对象

    探索四个属性

    1. 准备类

    @interface LGPerson : NSObject
    {
    NSString *_hobby;
    }
    
    @property (nonatomic, copy) NSString *lgName;
    
    @end
    
    • 定义一个成员变量 _hobby
    • 定义一个属性 lgName
    • 我们知道编译器会自动生成一个 lgName 的getter和setter方法和一个带下划线的成员变量 _lgName

    2. 查看 class_data_bits_t bits 属性

    根据上面对objc_class的四个属性的分析, class_data_bits_t bits是在第四个属性的位置, 但不能仅根据属性字段的第四个位置的地址来输出它, 需要根据地址偏移来完成.
    首地址偏移32位, 可以找到 bits 属性的地址,

    p (class_data_bits_t *)(0x1000039a0+32)

    class_data_bits_t

    你肯定会有一个疑问, 为什么这里要强转成 class_data_bits_t * 而不是class_data_bits_t.

    我们来看下面一行代码

    class_rw_t *data() { 
        return bits.data();
    }
    

    data返回的是是一个指针类的数据, 我们可以猜测这里有一大坨的数据, 怎么读取这一坨数据, 自然就是我们的指针了, 可能有其他理论依据, 但这里作为一个探索分析就只能这么分析了。

    【注意】在我们自己的iOS APP工程里面是无法分析的,原因是因为没有导入objc源码,所以无法找到 class_data_bits_t 类。

    3. 查看 class_rw_t bits.data() 数据源

    上面的截图中可以知道, 1表示 `bits`的指针, 那么就可以通过 `1` 来读取

    p $1->data()

    data

    4. 分析 class_rw_t 结构

    struct class_rw_t {
        // Be warned that Symbolication knows the layout of this structure.
        uint32_t flags;
        uint32_t version;
    
        const class_ro_t *ro;
    
        method_array_t methods;
        property_array_t properties;
        protocol_array_t protocols;
    
        Class firstSubclass;
        Class nextSiblingClass;
    
        char *demangledName;
    
    #if SUPPORT_INDEXED_ISA
        uint32_t index;
    #endif
    ...
    

    在这里看到了一个class_ro_t *ro, 不知道是什么, 但是既然是作为指针出现在这里, 一定是有它的意义的, 后面再做分析.
    除了ro, 还有比较熟悉的几个属性,

    • method_array_t methods: 方法数组
    • property_array_t properties: 属性数组
    • protocol_array_t protocols: 协议数组

    在上面的截图中 (class_rw_t *) $2 = 0x0000000101e68ef0, $2表示的是, class_rw_t *的指针, 那么可以尝试输出这个指针的值

    p *$2

    class_rw_t

    5. 同样的方式分析ro

    找到 ro 的指针
    p $3.ro
    打印 ro 的信息
    p *$4

    ro

    6. 探索LGPerson中的数据

    • 成员变量 ivar

      找到 ivars 的指针
      $4->ivars
      打印 ivars 的信息
      p *$6

      ivar_list_t

      ivar_list_t中有个count = 2, 而我们知道我们定义的成员变量个数刚好为2,这里只输出了第一个, 所以下面要输出全部的成员变量.

      p $7.get(1)

      ivars
    • 方法 method
      同样的操作, 找到了编译器为我们自动生成的getter和setter方法


      method
    • 属性 properties
      同样的操作, 找到了lgName存在的地方


      properties

    总结

    这篇文章记录的是怎么探索类的结构,我们常用的数据都是在哪里、怎么存储的,没有对加载时机做分析(也即什么时候加载到这些地方),后面会讲。

    探索完类的结构之后,有一种豁然开朗的感觉,以前都是靠猜测来分析属性、方法等存在哪里,现在能从内存中取出来,相当于是打开了潘多拉魔盒。

    相关文章

      网友评论

          本文标题:02--对象本质04--类的结构

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