美文网首页
OC类的结构 objc_class

OC类的结构 objc_class

作者: Onego | 来源:发表于2019-12-25 01:13 被阅读0次

    objc_class 静态结构分析

    struct objc_class : objc_object {
        // Class ISA; 
        Class superclass; 
        cache_t cache; 
        class_data_bits_t bits;  
    }
    

    今天看看 objc_class 中到底有哪些东西,去掉了结构体函数也就几个成员

    • isaobjc_object中继承而来
    • superclass 存放的是父类的指针
    • cache 存放着一些使用过的方法缓存
    • bits 存的是class_rw_t的指针,还有rr/alloc的一个标记,和isa有点类似

    类数据存储结构

    //在 class_data_bits_t 中主要的就是 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
    }
    
    struct class_ro_t {
        uint32_t flags;
        uint32_t instanceStart;
        uint32_t instanceSize;
    #ifdef __LP64__
        uint32_t reserved;
    #endif
    
        const uint8_t * ivarLayout;
        
        const char * name;
        method_list_t * baseMethodList;
        protocol_list_t * baseProtocols;
        const ivar_list_t * ivars;
    
        const uint8_t * weakIvarLayout;
        property_list_t *baseProperties;
    
        method_list_t *baseMethods() const {
            return baseMethodList;
        }
    };
    

    猜测:

    1. 这两个结构记录着 属性、方法、成员变量
    2. class_rw_t 应该是 readwrite,用来存储扩展 协议、方法、成员变量,并含有只读的class_ro_t ro
    3. class_ro_t 应该是 readonly,经过一旦编译就不会改变

    跑起来验证我的猜想

    //定义的类结构
    @interface LGPerson : NSObject{
        NSString *hobby;
    }
    
    @property (nonatomic, copy) NSString *nickName;
    
    - (void)sayHello;
    + (void)sayHappy;
    @end
    
    //LGPerson *person = [LGPerson new];
    
    1. 准备工作 找到类对象
    //查看person对象内存
    (lldb) x/2gx person
    0x101e07f90: 0x001d8001000023b5 0x0000000000000000
    
    //拿到 isa,换算成类对象地址  #   define ISA_MASK        0x00007ffffffffff8ULL
    (lldb) p/x 0x001d8001000023b5 & 0x00007ffffffffff8ULL
    (unsigned long long) $2 = 0x00000001000023b0
    (lldb) po 0x00000001000023b0
    LGPerson
    
    
    1. 类对象内存数据
    //LGPerson 0x00000001000023b0
    //查看 LGPerson类对象内存数据
    (lldb) x/2gx 0x00000001000023b0
    0x1000023b0: 0x001d800100002389 0x0000000100b37140
    
    1. 确实数据具体的位置
    objc_class 各中数据的大小
    struct objc_class : objc_object {
        Class  isa;  // size 8 ,pointer
        Class superclass;  // size 8 ,pointer
        cache_t cache;  // size 16 , struct
        class_data_bits_t bits;  // size 8 , struct
    }
    
    typedef uint32_t mask_t; // x86_64 & arm64
    struct cache_t {
        struct bucket_t *_buckets;  // size 8
        mask_t _mask;  // size 4
        mask_t _occupied; // size 4
    }
    
    struct class_data_bits_t {
        uintptr_t bits; // size 8
    }
    
    

    汇总一下

    名称 isa superclass cache bits
    类型 Class Class cache_t class_data_bits_t
    大小 8 8 16 8

    其中 cache_tbits 的是结构体不是指针,struct大小根据成员的大小而变换。

    那么偏移量的计算就很简单了

    1. 偏移到 bits 查看
    • 那么只要在类对象地址基础上偏移32就是bits的地址,验证一下
    //LGPerson 0x00000001000023b0
    //偏移32 查看bits 0x00000001000023b0 + 0d32 = 0x00000001000023d0
    (lldb) x/2gx 0x00000001000023d0
    0x1000023d0: 0x0000000101e062f4 0x0000000000000000
    
    //0x00000001020012f4 就是bits的地址
    //想拿到 class_rw_t 需要经过MASK
    // #define FAST_DATA_MASK          0x00007ffffffffff8UL
    (lldb) p/x 0x0000000101e062f4 & 0x00007ffffffffff8UL
    (unsigned long) $6 = 0x0000000101e062f0
    //将计算你结果 强转 class_rw_t *
    (lldb) p (class_rw_t *)0x0000000101e062f0
    (class_rw_t *) $7 = 0x0000000101e062f0
    
    
    

    当然也可以用 objc_object 中的函数 data() 拿到 class_rw_t *,效果是一样的

    (lldb) p (class_data_bits_t *)0x1000023d0
    (class_data_bits_t *) $5 = 0x00000001000023d0
    // 调用class_data_bits_t 中的data()函数
    (lldb) p $5->data()
    (class_rw_t *) $10 = 0x0000000101e062f0
    
    1. 查看 class_rw_t 中的数据
    (class_rw_t *) $10 = 0x0000000101e062f0
    (lldb) p *$5
    (class_rw_t) $12 = {
      flags = 2148139008
      version = 0
      ro = 0x0000000100002308
      methods = {
        list_array_tt<method_t, method_list_t> = {
           = {
            list = 0x0000000100002240
            arrayAndFlag = 4294976064
          }
        }
      }
      properties = {
        list_array_tt<property_t, property_list_t> = {
           = {
            list = 0x00000001000022f0
            arrayAndFlag = 4294976240
          }
        }
      }
      protocols = {
        list_array_tt<unsigned long, protocol_list_t> = {
           = {
            list = 0x0000000000000000
            arrayAndFlag = 0
          }
        }
      }
      firstSubclass = nil
      nextSiblingClass = NSUUID
      demangledName = 0x0000000100001f80 "LGPerson"
    }
    
    1. class_rw_t.properties
    //打印 class_rw_t 的 properties
    (lldb) p $12.properties
    (property_array_t) $13 = {
      list_array_tt<property_t, property_list_t> = {
         = {
          list = 0x00000001000022f0
          arrayAndFlag = 4294976240
        }
      }
    }
    (lldb) p $13.list
    (property_list_t *) $14 = 0x00000001000022f0
    (lldb) p $14.first
    (property_t) $15 = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
      Fix-it applied, fixed expression was: 
        $14->first
    

    最终在 properties 中找到了 定义的属性 nickName

    1. class_rw_t.methods
    (lldb) p $12.methods
    (method_array_t) $17 = {
      list_array_tt<method_t, method_list_t> = {
         = {
          list = 0x0000000100002240
          arrayAndFlag = 4294976064
        }
      }
    }
    (lldb) p $17.list
    (method_list_t *) $18 = 0x0000000100002240
    (lldb) p $18->get(0)
    (method_t) $22 = {
      name = "sayHello"
      types = 0x0000000100001f8b "v16@0:8"
      imp = 0x0000000100001b90 (LGTest`-[LGPerson sayHello] at LGPerson.m:13)
    }
    (lldb) p $18->get(1)
    (method_t) $23 = {
      name = ".cxx_destruct"
      types = 0x0000000100001f8b "v16@0:8"
      imp = 0x0000000100001c60 (LGTest`-[LGPerson .cxx_destruct] at LGPerson.m:11)
    }
    (lldb) p $18->get(2)
    (method_t) $24 = {
      name = "nickName"
      types = 0x0000000100001f93 "@16@0:8"
      imp = 0x0000000100001bf0 (LGTest`-[LGPerson nickName] at LGPerson.h:17)
    }
    (lldb) p $18->get(3)
    (method_t) $25 = {
      name = "setNickName:"
      types = 0x0000000100001f9b "v24@0:8@16"
      imp = 0x0000000100001c20 (LGTest`-[LGPerson setNickName:] at LGPerson.h:17)
    }
    

    最终在 methods 中找到了

    • 自己定义的sayHello方法
    • 系统生成的c++ 析构方法nickName
    • 系统生成的get方法.cxx_destruct
    • 系统生成的set方法 setNickName:
    1. 查看 class_ro_t 中的数据
    (lldb) p *$12.ro
    (const class_ro_t) $29 = {
      flags = 388
      instanceStart = 8
      instanceSize = 24
      reserved = 0
      ivarLayout = 0x0000000100001f89 "\x02"
      name = 0x0000000100001f80 "LGPerson"
      baseMethodList = 0x0000000100002240
      baseProtocols = 0x0000000000000000
      ivars = 0x00000001000022a8
      weakIvarLayout = 0x0000000000000000
      baseProperties = 0x00000001000022f0
    }
    
    • 这里有个 const 说明class_ro_t是一个常量。
    • 这里存放这类名 等信息
    1. class_ro_t.ivars
    (lldb) p *$29.ivars
    (const ivar_list_t) $31 = {
      entsize_list_tt<ivar_t, ivar_list_t, 0> = {
        entsizeAndFlags = 32
        count = 2
        first = {
          offset = 0x0000000100002378
          name = 0x0000000100001e64 "hobby"
          type = 0x0000000100001fa6 "@\"NSString\""
          alignment_raw = 3
          size = 8
        }
      }
    }
    (lldb) p $31.get(0)
    (ivar_t) $33 = {
      offset = 0x0000000100002378
      name = 0x0000000100001e64 "hobby"
      type = 0x0000000100001fa6 "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
    (lldb) p $31.get(1)
    (ivar_t) $34 = {
      offset = 0x0000000100002380
      name = 0x0000000100001e6a "_nickName"
      type = 0x0000000100001fa6 "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
    (lldb) 
    

    这里找到了两个成员变量

    • 自定义的成员变量 hobby
    • 系统生成的成员变量 _nickName
    1. class_ro_t.baseMethodList
    (lldb) p *$29.baseMethodList
    (method_list_t) $36 = {
      entsize_list_tt<method_t, method_list_t, 3> = {
        entsizeAndFlags = 26
        count = 4
        first = {
          name = "sayHello"
          types = 0x0000000100001f8b "v16@0:8"
          imp = 0x0000000100001b90 (LGTest`-[LGPerson sayHello] at LGPerson.m:13)
        }
      }
    }
    (lldb) p $36.get(0)
    (method_t) $37 = {
      name = "sayHello"
      types = 0x0000000100001f8b "v16@0:8"
      imp = 0x0000000100001b90 (LGTest`-[LGPerson sayHello] at LGPerson.m:13)
    }
    (lldb) p $36.get(1)
    (method_t) $38 = {
      name = ".cxx_destruct"
      types = 0x0000000100001f8b "v16@0:8"
      imp = 0x0000000100001c60 (LGTest`-[LGPerson .cxx_destruct] at LGPerson.m:11)
    }
    (lldb) p $36.get(2)
    (method_t) $39 = {
      name = "nickName"
      types = 0x0000000100001f93 "@16@0:8"
      imp = 0x0000000100001bf0 (LGTest`-[LGPerson nickName] at LGPerson.h:17)
    }
    (lldb) p $36.get(3)
    (method_t) $40 = {
      name = "setNickName:"
      types = 0x0000000100001f9b "v24@0:8@16"
      imp = 0x0000000100001c20 (LGTest`-[LGPerson setNickName:] at LGPerson.h:17)
    }
    

    这里居然和 class_rw_t中的一模一样

    class_rw_t 是可以在运行时来拓展类的一些属性、方法和协议等内容。
    class_ro_t 是在编译时就已经确定了的,存储的是类的成员变量、属性、方法和协议等内容。
    运行时,会将 ro 中的数据 copy 到 rw 中。

    1. 类方法在哪里?

    我在 LGPerson中定义了 + (void)sayHappy;类方法但是好像并没有找到,不出意外的话应该在元类对象中。

    // 0x00000001000023b0 LGPerson类对象
    (lldb) x/4gx 0x00000001000023b0
    0x1000023b0: 0x001d800100002389 0x0000000100b37140
    0x1000023c0: 0x00000001003da260 0x0000000000000000
    //拿到LGPerson 元类对象
    (lldb) p/x 0x001d800100002389 & 0x00007ffffffffff8ULL
    (unsigned long long) $41 = 0x0000000100002388
    //打印元类内存数据
    (lldb) x/6gx 0x0000000100002388
    0x100002388: 0x001d800100b370f1 0x0000000100b370f0
    0x100002398: 0x0000000100f42780 0x0000000300000007
    0x1000023a8: 0x0000000101e062b0 0x001d800100002389
    //拿到 元类的 class_rw_t *
    (lldb) p (class_rw_t *)0x1000023a8
    (class_rw_t *) $43 = 0x00000001000023a8
    (lldb) p *$43
    (class_rw_t) $44
    p $44.ro
    (const class_ro_t *) $45 = 0x001d800100002389
    (lldb) p *$45
    //error: Couldn't apply expression side effects : Couldn't dematerialize a result variable: couldn't read its memory
     //最后发现 元类的 ro 好像不让访问
    

    换一种方式证明

      const char *className = class_getName(pClass);
        Class metaClass = objc_getMetaClass(className);
    
        Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
        Method method4 = class_getClassMethod(metaClass, @selector(sayHappy)); 
    
    // 输出 0x1000021e0-0x1000021e0
    

    这结果太尴尬了都能找到,看了一下 class_getClassMethod的源码找到的问题所在

    Method class_getClassMethod(Class cls, SEL sel)
    {
        if (!cls  ||  !sel) return nil;
        return class_getInstanceMethod(cls->getMeta(), sel);
    }
    
    Class getMeta() {
        //如果是元类就返回 this,不是元类就返回 isa
        if (isMetaClass()) return (Class)this;
        else return this->ISA();
    }
    

    class_getClassMethod 找元类中找到了sayHappy的实例方法,所以可以得出类方法是保存在元类中。

    class_ro_t 和 class_rw_t 的关系

    class_ro_t由编译阶段确定的只读数据
    class_rw_t是在运行时创建

    在什么时候创建 rw
    • realizeClass中对rw进行了第一次初始化
    • 在方法慢速查找的时候,如果类对象未实现isRealized就会触发realizeClass
    realizeClass 调用栈
    >objc_alloc
      > callAlloc
        > (汇编)_objc_msgSend_uncached
          > lookUpImpOrForward
            > realizeClass
    
    realizeClass 中的rw和ro
    static Class realizeClass(Class cls)
    {
        ......
        
        //初次读取cls->data() 指向的是 class_ro_t
        //之后的读取cls->data() 指向的是 class_rw_t
        //两种结构体 class_ro_t 和 class_rw_t 第一个成员都是flags,强转至少保证了flag的正常获取
        ro = (const class_ro_t *)cls->data();
        if (ro->flags & RO_FUTURE) { //判断是否是rw
           //如果rw已经分配了内存,则rw指向cls->data(),然后将rw的ro指针指向之前最开始的ro
            rw = cls->data();
            ro = cls->data()->ro;
            //修改 rw的flags
            cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
        } else {
            //如果rw并不存在,则为rw分配空间
            rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
            //将rw->ro设置为原始的ro
            rw->ro = ro;
            //设置 rw的flags
            rw->flags = RW_REALIZED|RW_REALIZING;
            //修改cls->data(),也就是让cls->data()指向rw
            cls->setData(rw);
        }
        isMeta = ro->flags & RO_META;
        ......
    }
    

    总结

    • 实例方法存放在类对象中 ,类方法存放在元类对象中
    • 类的方法、协议、属性都存放在类对象的class_rw_t中
    • class_ro_t 存放着编译时确定的方法、协议、属性、成员变量

    遗留的问题

    • 为什么元类对象的 ro 不能访问

    方案一

    realizeClass的时候看到了元类处理rorw的过程,直接拿其中的ro

    // 用来判断是否是元类的Mask #define RO_META               (1<<0)
    (lldb) po ro->flags & (1<<0)
    1
    (lldb) p *ro
    (const class_ro_t) $0 = {
      flags = 389
      instanceStart = 40
      instanceSize = 40
      reserved = 0
      ivarLayout = 0x0000000000000000
      name = 0x0000000100001f85 "LGPerson"
      baseMethodList = 0x00000001000021d8
      baseProtocols = 0x0000000000000000
      ivars = 0x0000000000000000
      weakIvarLayout = 0x0000000000000000
      baseProperties = 0x0000000000000000
    }
    (lldb) p $0.baseMethodList
    (method_list_t *const) $4 = 0x00000001000021d8
    (lldb) p $4->get(0)
    (method_t) $5 = {
      name = "sayHappy"
      types = 0x0000000100001f90 "v16@0:8"
      imp = 0x0000000100001bc0 (LGTest`+[LGPerson sayHappy] at LGPerson.m:17)
    }
    

    方案二

    好吧必须承认的是我粗心了,元类偏移32的指针直接当成class_rw_t *来处理,实际上是class_data_bits_t *

    //LGPerson的元类  0x0000000100002390
    (lldb) x/6gx 0x0000000100002390
    0x100002390: 0x001d800100b370f1 0x0000000100b370f0
    0x1000023a0: 0x000000010180a1a0 0x0000000400000007
    0x1000023b0: 0x000000010192ee50 0x001d800100002391
    (lldb) p (class_data_bits_t *)0x1000023b0
    (class_data_bits_t *) $14 = 0x00000001000023b0
    (lldb) p *$14
    (class_data_bits_t) $15 = (bits = 4321373776)
    (lldb) p $14->data()
    (class_rw_t *) $17 = 0x000000010192ee50
    (lldb) p $17->ro
    (const class_ro_t *) $18 = 0x00000001000021f8
    (lldb) p *$18
    (const class_ro_t) $19 = {
      flags = 389
      instanceStart = 40
      instanceSize = 40
      reserved = 0
      ivarLayout = 0x0000000000000000
      name = 0x0000000100001f85 "LGPerson"
      baseMethodList = 0x00000001000021d8
      baseProtocols = 0x0000000000000000
      ivars = 0x0000000000000000
      weakIvarLayout = 0x0000000000000000
      baseProperties = 0x0000000000000000
    }
    (lldb) p $19.baseMethodList
    (method_list_t *const) $20 = 0x00000001000021d8
    (lldb) p $20->get(0)
    (method_t) $21 = {
      name = "sayHappy"
      types = 0x0000000100001f90 "v16@0:8"
      imp = 0x0000000100001bc0 (LGTest`+[LGPerson sayHappy] at LGPerson.m:17)
    }
    //激动啊 折腾了两天,终于看到了美丽的sayHappy
    

    收工

    相关文章

      网友评论

          本文标题:OC类的结构 objc_class

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