美文网首页iOS
iOS底层探索之类的结构(中)

iOS底层探索之类的结构(中)

作者: 俊而不逊 | 来源:发表于2021-06-22 18:05 被阅读0次
    类的结构探索分析.png

    在上一篇博客里面iOS底层探索之类的结构(上)已经大致的了解了类的结构

    类的结构

    struct objc_class : objc_object {
      objc_class(const objc_class&) = delete;
      objc_class(objc_class&&) = delete;
      void operator=(const objc_class&) = delete;
      void operator=(objc_class&&) = delete;
        // Class ISA;
        Class superclass;
        cache_t cache;             // formerly cache pointer and vtable
        class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    }
    

    class_data_bits

    我们主要探究是class_data_bits_t bits,在bits里面有我们关心的类的信息。
    那么我们怎么拿呢?先看看下面这个JPPerson

    @interface JPPerson : NSObject
    {
        int age;
    }
    @property (nonatomic, copy) NSString *name;
    @property (nonatomic, copy) NSString *hobby;
    
    - (void)sayHello;
    + (void)sayNB;
    @end
    @implementation JPPerson
    
    - (instancetype)init{
         if (self = [super init]) {
              self.name = @"reno";
         }
         return self;
    }
    - (void)sayHello{
    }
    + (void)sayNB{
    }
    @end
    

    使用lldb调试 x/4gx 命令打印了JPPerson这个类的内存信息

    调试结果
    第一个是isa;第二个是superclasspo 出来是NSObjectJPPerson 是继承NSObject的;那么以此类推,第三个是cache,第四个是bits

    bits

    我们要了解bits里面的data信息,该怎么拿呢?光知道一个地址也不行啊?那么我们既然知道了isa(首地址),是不是就可以通过指针偏移内存偏移拿到呢?那么要偏移多少个呢?

    类的结构

    要想拿到bits,指针在内存中必须要平移指向bits,我知道isa是8个字节长度,superclass也是8个字节长度,那么cache_t呢?看看内部结构分析下

    cache_t

    cache_t内存大小
    分析得到cache_t是16,那么加isasuperclass一共就是32个字节的长度。
    (lldb) x/4gx JPPerson.class
    0x100008358: 0x0000000100008380 0x000000010036a140
    0x100008368: 0x00000001003623c0 0x0000802800000000
    (lldb) p/x 0x100008358+0x20
    (long) $1 = 0x0000000100008378
    (lldb) p (class_data_bits_t*)0x0000000100008378
    (class_data_bits_t *) $2 = 0x0000000100008378
    (lldb) p $2->data()
    (class_rw_t *) $3 = 0x0000000101b06bc0
    (lldb) p *$3
    (class_rw_t) $4 = {
      flags = 2148007936
      witness = 0
      ro_or_rw_ext = {
        std::__1::atomic<unsigned long> = {
          Value = 4295000144
        }
      }
      firstSubclass = nil
      nextSiblingClass = NSUUID
    }
    

    好尴尬啊!class_rw_t里面没有看到我们该看到的信息啊!

    class_rw_t

    class_rw_t结构
    从没有错啊!打印的信息和在class_rw_t源码里面的结构是一样的啊?
    那怎么没有呢?我们刚打印的是结构信息,里面的方法没有找到,我们继续去看看源码,看看有没有打印属性的方法。
    class_rw_t方法
    还真有方法,你说巧不巧啊!嘿嘿
    嘿嘿
    那么靓仔,直接调用properties()不就可以了吗!

    properties

    (lldb) p $3.properties()
    (const property_array_t) $5 = {
      list_array_tt<property_t, property_list_t, RawPtr> = {
         = {
          list = {
            ptr = 0x0000000100008198
          }
          arrayAndFlag = 4295000472
        }
      }
    }
      Fix-it applied, fixed expression was: 
        $3->properties()
    (lldb) 
    

    调用properties()方法得到property_array_t

    property_array_t

    class property_array_t : 
        public list_array_tt<property_t, property_list_t, RawPtr>
    {
        typedef list_array_tt<property_t, property_list_t, RawPtr> Super;
    
     public:
        property_array_t() : Super() { }
        property_array_t(property_list_t *l) : Super(l) { }
    };
    

    list_array_tt

    list_array_tt<property_t, property_list_t, RawPtr>

    list_array_tt

    知道了结构,我们就一层一层的往下扒

    (lldb) p $3.properties()
    (const property_array_t) $5 = {
      list_array_tt<property_t, property_list_t, RawPtr> = {
         = {
          list = {
            ptr = 0x0000000100008198
          }
          arrayAndFlag = 4295000472
        }
      }
    }
      Fix-it applied, fixed expression was: 
        $3->properties()
    (lldb) p $5.list
    (const RawPtr<property_list_t>) $6 = {
      ptr = 0x0000000100008198
    }
    (lldb) p $6.ptr
    (property_list_t *const) $7 = 0x0000000100008198
    (lldb) p $7*
    error: <user expression 9>:2:1: expected expression
    ;
    ^
    (lldb) p *$7
    (property_list_t) $8 = {
      entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 2)
    }
    (lldb) p $8.get(0)
    (property_t) $9 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
    (lldb) p $8.get(1)
    (property_t) $10 = (name = "hobby", attributes = "T@\"NSString\",C,N,V_hobby")
    (lldb) 
    

    还有谁?靓仔看到没有,JPPerson的属性打印出来了,我们要的类的信息成功输出来了!我这该死的魅力啊!

    在这里插入图片描述

    methods()

    上面打印了属性,接下来该看看方法了

    (lldb) p $3.methods()
    (const method_array_t) $15 = {
      list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
         = {
          list = {
            ptr = 0x0000000100008098
          }
          arrayAndFlag = 4295000216
        }
      }
    }
      Fix-it applied, fixed expression was: 
        $3->methods()
    (lldb) p $15.list.ptr
    (method_list_t *const) $18 = 0x0000000100008098
    (lldb) p *$18
    (method_list_t) $19 = {
      entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 6)
    }
    (lldb) p $19.get(0)
    (method_t) $20 = {}
    (lldb) p $19.get(0)
    (method_t) $21 = {}
    (lldb) p $19.get(1)
    (method_t) $22 = {}
    

    what ??什么鬼啊?怎么get打印不出来啊?别急继续探索下去

    method_t

    method_t
    在源码里面发现了method_t结构体,里面嵌套了一个big结构体,big里面有个SEL 还有imp
    big
    那么可以通过method_t结构体拿到big,就可以获取到里面的方法,源码里面也发现了获取big方法big()

    big()

    (lldb) p $19.get(0).big
    (method_t::big) $23 = {
      name = "sayHello"
      types = 0x0000000100003f94 "v16@0:8"
      imp = 0x0000000100003cd0 (JPObjcBuild`-[JPPerson sayHello])
    }
      Fix-it applied, fixed expression was: 
        $19.get(0).big()
    (lldb) p $19.get(1).big
    (method_t::big) $24 = {
      name = "hobby"
      types = 0x0000000100003f8c "@16@0:8"
      imp = 0x0000000100003d40 (JPObjcBuild`-[JPPerson hobby])
    }
      Fix-it applied, fixed expression was: 
        $19.get(1).big()
    (lldb) p $19.get(2).big
    (method_t::big) $25 = {
      name = "setHobby:"
      types = 0x0000000100003f9c "v24@0:8@16"
      imp = 0x0000000100003d70 (JPObjcBuild`-[JPPerson setHobby:])
    }
      Fix-it applied, fixed expression was: 
        $19.get(2).big()
    (lldb) p $19.get(3).big
    (method_t::big) $26 = {
      name = "init"
      types = 0x0000000100003f8c "@16@0:8"
      imp = 0x0000000100003c70 (JPObjcBuild`-[JPPerson init])
    }
      Fix-it applied, fixed expression was: 
        $19.get(3).big()
    (lldb) p $19.get(4).big
    (method_t::big) $27 = {
      name = "name"
      types = 0x0000000100003f8c "@16@0:8"
      imp = 0x0000000100003ce0 (JPObjcBuild`-[JPPerson name])
    }
      Fix-it applied, fixed expression was: 
        $19.get(4).big()
    (lldb) p $19.get(5).big
    (method_t::big) $28 = {
      name = "setName:"
      types = 0x0000000100003f9c "v24@0:8@16"
      imp = 0x0000000100003d10 (JPObjcBuild`-[JPPerson setName:])
    }
      Fix-it applied, fixed expression was: 
        $19.get(5).big()
    

    干的漂亮!哈哈😁,方法列表里面count值为6,一共有6个都打印了出来了,包括setter方法和getter方法,靓仔就问你服不服!

    服不服

    那么有的靓仔肯定不服了,我怎么没有看到方法(+方法)和成员变量打印出来呢?你在这里装什么大一瓣蒜啊!

    好,不服是吧!那么我们接着往下探索。

    ivars

    属性和成员变量在内存中存放的位置是不一样的,在WWDC2020里面介绍了Clean MemoryDirty Memory

    Clean Memory

    clean memory 加载后不会发生改变的内存class_ro_t 就属于clean memory,因为它是只读的不会,不会对齐内存进行修改clean memory
    是可以进行移除的,从而节省更多的内存空间,因为如果你有需要clean memory,系统可以从磁盘中重新加载

    Dirty Memory

    dirty memory 是指在进程运行时会发生改变的内存 类结构一经使用就会变成 dirty memory,因为运行时会向它写入新的数据。例如创建一个新的方法缓存并从类中指向它,初始化类相关的子类和父类dirty memory是类数据被分成两部分的主要原因

    dirty memory要比clean memory昂贵的多,只要程序运行它就必须一直存在,通过分离出那些不会被改变的数据,可以把大部分的类数据存储为clean memory

    请看下面这个图(WWDC2020视频里面截取的)

    class_ro_t

    从图中我们知道成员变量在class_ro_t里面,那么我们打印一下看看

    ro

    既然里面有ivars成员信息,那么再打印出来看看

    (lldb) p $6.ivars
    (const ivar_list_t *const) $7 = 0x0000000100008130
    (lldb) p *$7
    (const ivar_list_t) $8 = {
      entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 3)
    }
    (lldb) p $7.get(0)
    (ivar_t) $9 = {
      offset = 0x0000000100008340
      name = 0x0000000100003f2a "age"
      type = 0x0000000100003f7e "i"
      alignment_raw = 2
      size = 4
    }
      Fix-it applied, fixed expression was: 
        $7->get(0)
    (lldb) p $7.get(1)
    (ivar_t) $10 = {
      offset = 0x0000000100008348
      name = 0x0000000100003f2e "_name"
      type = 0x0000000100003f80 "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
      Fix-it applied, fixed expression was: 
        $7->get(1)
    (lldb) p $7.get(2)
    (ivar_t) $11 = {
      offset = 0x0000000100008350
      name = 0x0000000100003f34 "_hobby"
      type = 0x0000000100003f80 "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
      Fix-it applied, fixed expression was: 
        $7->get(2)
    (lldb) 
    

    成员信息的数据类型,名称、内存大小,都打印出来了。

    类方法

    在上面的测试中,JPPerson的对象方法可以正常打印出来,是在JPPerson.class中获取打印的。在MachOView中,也是可以看到,类方法确实存在的。

    在这里插入图片描述

    那么类方法也就是加号方法,是否是在元类中的呢?对象方法在类中,类方法在元类中,这不是很符合逻辑吗?好,那就去探索验证一下

    (lldb) x/4gx JPPerson.class
    0x100008358: 0x0000000100008380 0x000000010036a140
    0x100008368: 0x00000001003623c0 0x0000802800000000
    (lldb) p/x 0x0000000100008380 + 0x20
    (long) $19 = 0x00000001000083a0
    (lldb) p/x (class_data_bits_t*)0x00000001000083a0
    (class_data_bits_t *) $20 = 0x00000001000083a0
    (lldb) p $20->data()
    (class_rw_t *) $21 = 0x0000000101337080
    (lldb) p *$21
    (class_rw_t) $22 = {
      flags = 2684878849
      witness = 1
      ro_or_rw_ext = {
        std::__1::atomic<unsigned long> = {
          Value = 4315117329
        }
      }
      firstSubclass = 0x00000001000083a8
      nextSiblingClass = 0x00007fff80111eb0
    }
    (lldb) p $22.methods()
    (const method_array_t) $23 = {
      list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
         = {
          list = {
            ptr = 0x0000000100008208
          }
          arrayAndFlag = 4295000584
        }
      }
    }
    (lldb) p $23.list.ptr
    (method_list_t *const) $24 = 0x0000000100008208
    (lldb) p *$24
    (method_list_t) $25 = {
      entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 1)
    }
    (lldb) p $25.get(0).big()
    (method_t::big) $26 = {
      name = "sayNB"
      types = 0x0000000100003f94 "v16@0:8"
      imp = 0x0000000100003da0 (JPObjcBuild`+[JPPerson sayNB])
    }
    (lldb) 
    

    哈哈,还有谁


    还有谁?

    这波操作,就问你服不服!

    总结

    • 元类isa指向:元类isa->根元类isa->根元类(NSObject的元类)
    • 元类继承关系:类继承isa->根元类isa->NSObject->nil
    • 类中有isa、superclass、chche、bits 成员变量,
    • bits 存储着属性列表、方法列表、成员变量列表、协议列表等

    更多内容持续更新

    iOS底层探索之类的结构(下)

    🌹 请动动你的小手,点个赞👍🌹

    🌹 喜欢的可以来一波,收藏+关注,评论 + 转发,以免你下次找不到我,哈哈😁🌹

    🌹欢迎大家留言交流,批评指正,互相学习😁,提升自我🌹

    相关文章

      网友评论

        本文标题:iOS底层探索之类的结构(中)

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