美文网首页
类结构探究(二)-- bits结构探究

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

作者: iOSer_jia | 来源:发表于2020-09-16 11:58 被阅读0次

    上一篇文章中我们已经探究了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<struct bucket_t *> _buckets;
        explicit_atomic<mask_t> _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<unsigned long> = 0x0000000100002088
      }
      firstSubclass = nil
      nextSiblingClass = nil
    }
    

    properties

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

    (lldb) p $4.properties()
    (const property_array_t) $5 = {
      list_array_tt<property_t, property_list_t> = {
         = {
          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<property_t, property_list_t, 0> = {
        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<ivar_t, ivar_list_t, 0> = {
        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<method_t, method_list_t> = {
         = {
          list = 0x00000001000020d0
          arrayAndFlag = 4294975696
        }
      }
    }
    
    (lldb) p $14.list
    (method_list_t *const) $15 = 0x00000001000020d0
    
    (lldb) p *$15
    (method_list_t) $16 = {
      entsize_list_tt<method_t, method_list_t, 3> = {
        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<unsigned long> = 4294975520
      }
      firstSubclass = nil
      nextSiblingClass = nil
    }
    // 获取methods
    (lldb) p $26.methods()
    (const method_array_t) $27 = {
      list_array_tt<method_t, method_list_t> = {
         = {
          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<method_t, method_list_t, 3> = {
        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结构探究

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