美文网首页
iOS底层原理探究05-类的底层原理isa链&继承链&类的内存结

iOS底层原理探究05-类的底层原理isa链&继承链&类的内存结

作者: superFool | 来源:发表于2021-08-05 17:40 被阅读0次

    isa指向分析

    通过《iOS底层原理探究04-OC对象的本质&联合体位域&isa分析》我们对isa已经有了一定的了解,现在我们来研究下isa具体的指向情况。

    对象的isa指向

    对象的isa指向类这个我们都知道下面就来验证一下
    这里需要知道nopointerisa的存在,普通的isa是直接指向类的,但是得添加OBJC_DISABLE_NONPOINTER_ISA = YES参数能会使用普通的isa否则默认会使用nopointerisa

    image.png
    当我们不使用nopointerisaisa是直接指向类的
    image.png
    • 对象中第一个变量就是isa0x0000000100008700
    • nopointerisaisa指向类 po 0x0000000100008700打印LGPerson
      image.png
      首先x/4gx查看p对象的内存,首地址0x011d800100008365就是pisa指针,nopointerisa需要&上ISA_MASK才是isa指向的类的地址(这些在上一篇里都有介绍),po输出了LGPerson,后面又通过p/x验证了一下LGPerson.classisa & ISA_MASK的地址,结果是一样的,这样就充分验证了对象pisa指向它的类LGPerson

    类的isa指向

    现在我们验证了对象的isa是指向类的,那么类的isa又指向什么呢,我们来继续探究

    image.png
    • x/4gx查看类的内存情况得到类的isa地址(即内存的首地址) 0x0000000100008338
    • po输出类的isa & ISA_MASK输出了LGPerson,这个LGPerson还是当前类的吗?
    • p/x 0x0000000100008338 & 0x00007ffffffffff8通过p/x isa & ISA_MASK输出地址,发现是0x0000000100008338和之前的$12 = 0x0000000100008360 LGPerson并不是通一个地址,因此这两个LGPerson并不是相同的。

    通过上面的一通操作我们得到了这样一个结果0x00000001000083600x0000000100008338 都输出 LGPerson,这里我们大胆猜想一下 ,是不是类和对象一样在内存里也会存在很多个,接下来我们验证一下这个猜想

    image.png
    我们使用各种方法得到的类打印出来的地址都是同一个,说明我们的猜想不正确。
    用烂苹果(MachOView)看一下吧
    image.png
    把Products文件夹下编译生成的可执行文件(002-isa分析)放到烂苹果里分析下
    image.png
    到符号表(Symbol Table)里查看
    • 0000000100008360_OBJC_ClASS(类)类型的LGPerson
    • 0000000100008338_OBJC_METACLASS(元类)类型的LGPerson
      这下就清晰了 一个是类,一个是元类,类的isa指向元类,这个元类我们代码里并没有创建,是系统编译的时候自动生成的,也就是说 对象 isa -> 类 isa -> 元类

    此时我会类的isa指向元类,那元类的isa指向哪里呢?接下来继续探究一下

    image.png
    输出元类的isa & ISA_MASK发现指向的是根元类NSObject,进一步输出跟元类的isa & ISA_MASK是指向自己的,这个我们输出NSObject类的地址发现和根元类NSObject的地址并不相同。至此我们可以得到这样一个结论
    isa走位图
    一般的类isa的走位图是这样的,那么根类NSObject的isa的走位图又是什么样的呢
    image.png
    可以看到NSObject还是有点区别的比普通的类少了一层
    NSObject的isa走位图
    结合到一起就得到了完整的isa走位图
    完整的isa走位图
    接下来看下继承链
    继承链
    运行结果说明
    • LGPerson元类的父类是NSObject元类
    • LGTeacher(LGPerson的子类)元类的父类是LGPerson元类
    • 根类NSObject没有父类
    • 根元类NSObject的父类是NSObject类
      至此继承链也已经很清晰了


      继承链

      结合isa走位图和继承链图得到苹果官网上的这张图


      isa流程图.png
      类的isa指向和继承链已经很清晰了。关于isa的走位和继承链就探索到这里,接下来开始探究类的内存结构。

    类的内存结构

    《iOS底层原理探究04-OC对象的本质&联合体位域&isa分析》里面我们已经知道了OC的类Class在底层是struct objc_class *类型,接下来就来看看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有好几百行代码,我们省略掉无关紧要的代码和方法代码(结构体的方法不在接口对象里存而是在方法区保存),直接研究他的成员变量,可以得到objc_class包含4个成员变量

    • ISA(从objc_object继承过来的)
    • superclass父类
    • cache 缓存
    • bits 类的数据
      第一个isa我们在前面的内容其实已经验证过了
      看下第二个成员变量是不是superclass
      image.png
      下面我们使用LGPerson类来做示例,看下属性、方法、成员变量在类里是怎么存的。
      image.png
      po第二个成员变量确实打印了父类NSObject
      cache我们先不看(下一篇重点介绍)先看bits,但是要想看bits就先得知道cache的大小,然后通过类的首地址+偏移量(ISA + superclass + cache)来找到bits的地址进而查看bits的内存结构。ISAsuperclass都是Class类型8字节 但是cache是个结构体它的大小要根据成员变量确定,所以要先简单看下它的结构确定大小。
    struct cache_t {
    private:
        explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
        union {
            struct {
                explicit_atomic<mask_t>    _maybeMask;
    #if __LP64__
                uint16_t                   _flags;
    #endif
                uint16_t                   _occupied;
            };
            explicit_atomic<preopt_cache_t *> _originalPreoptCache;
        };
    ...省略静态变量
    ...省略无关方法代码
    }
    

    因为静态变量是存在静态存储区的,方法存在方法区都不影响cache_t结构体的大小所以省略掉这些代码,下面就很清晰了就剩一个explicit_atomic<uintptr_t> _bucketsAndMaybeMask和联合体。

    • typedef unsigned long uintptr_t;可知_bucketsAndMaybeMask是8字节
    • 联合体中_maybeMask(mask_t) + _flags(uint16_t) + _occupied(uint16_t),mask_tuint32_t类型4字节 uint16_t是2字节 ,就是 4 + 2 + 2 = 8字节,联合体中另外一个成员_originalPreoptCache是指针类型也是8字节,所以联合体整体占用8字节(关于联合体看这里)
    • cache_t就是8 + 8 = 16字节

    那么拿到class的首地址 + 32就是bits的位置。

    image.png
    按我们之前的思路确实能获取到class_data_bits_t *类型的bits,但是怎么看这个数据结构呢。
    struct class_data_bits_t {
    ...省略无关代码
    public:
    
        class_rw_t* data() const {
            return (class_rw_t *)(bits & FAST_DATA_MASK);
        }
    ...省略无关代码
    }
    

    通过源码可以找到class_data_bits_t中存在data ()取数据的方法我们来试一下。

    image.png
    通过data()方法确实取到了class_rw_t,打印得到了class_rw_t的数据结构,再来看下class_rw_t的源码。
    struct class_rw_t {
        ...省略无关代码
        const method_array_t methods() const {
            auto v = get_ro_or_rwe();
            if (v.is<class_rw_ext_t *>()) {
                return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
            } else {
                return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods()};
            }
        }
    
        const property_array_t properties() const {
            auto v = get_ro_or_rwe();
            if (v.is<class_rw_ext_t *>()) {
                return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
            } else {
                return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
            }
        }
    
        const protocol_array_t protocols() const {
            auto v = get_ro_or_rwe();
            if (v.is<class_rw_ext_t *>()) {
                return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;
            } else {
                return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
            }
        }
    };
    
    • methods() 取方法列表方法
    • properties() 取属性列表方法
    • protocols() 取协议列表方法
      打印属性列表
      使用properties()方法获取到了property_array_t通过控制台打印的结构看到property_array_t里有个list结构,list里包含了ptr,深入进去得到了property_list_t类型的一个列表,使用get()方法查看list的内容
      image.png
      至此我们在类内存结构里找到了类的两个属性namehobby
      获取类里的方法
    (lldb) x/4gx LGPerson.class
    0x100008380: 0x00000001000083a8 0x000000010036c140
    0x100008390: 0x0000000100364360 0x0000802800000000
    (lldb) p/x 0x100008380+0x20
    (long) $1 = 0x00000001000083a0
    (lldb) p (class_data_bits_t *)0x00000001000083a0
    (class_data_bits_t *) $2 = 0x00000001000083a0
    (lldb) p $2->data()
    (class_rw_t *) $3 = 0x0000000101349aa0
    (lldb) p *$3
    (class_rw_t) $4 = {
      flags = 2148007936
      witness = 0
      ro_or_rw_ext = {
        std::__1::atomic<unsigned long> = {
          Value = 4295000344
        }
      }
      firstSubclass = nil
      nextSiblingClass = NSUUID
    }
    (lldb) p $4.methods()
    (const method_array_t) $5 = {
      list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
         = {
          list = {
            ptr = 0x0000000100008160
          }
          arrayAndFlag = 4295000416
        }
      }
    }
    (lldb) p $5.list
    (const method_list_t_authed_ptr<method_list_t>) $6 = {
      ptr = 0x0000000100008160
    }
    (lldb) p $6.ptr
    (method_list_t *const) $7 = 0x0000000100008160
    (lldb) p *$7
    (method_list_t) $8 = {
      entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 6)
    }
    (lldb) p $8.get(0).big()
    (method_t::big) $9 = {
      name = "sayNB"
      types = 0x0000000100003f77 "v16@0:8"
      imp = 0x0000000100003d40 (KCObjcBuild`-[LGPerson sayNB])
    }
    (lldb) p $8.get(1).big()
    (method_t::big) $10 = {
      name = "hobby"
      types = 0x0000000100003f6f "@16@0:8"
      imp = 0x0000000100003db0 (KCObjcBuild`-[LGPerson hobby])
    }
    (lldb) p $8.get(2).big()
    (method_t::big) $11 = {
      name = "setHobby:"
      types = 0x0000000100003f8b "v24@0:8@16"
      imp = 0x0000000100003de0 (KCObjcBuild`-[LGPerson setHobby:])
    }
    (lldb) p $8.get(3).big()
    (method_t::big) $12 = {
      name = "init"
      types = 0x0000000100003f6f "@16@0:8"
      imp = 0x0000000100003ce0 (KCObjcBuild`-[LGPerson init])
    }
    (lldb) p $8.get(4).big()
    (method_t::big) $13 = {
      name = "name"
      types = 0x0000000100003f6f "@16@0:8"
      imp = 0x0000000100003d50 (KCObjcBuild`-[LGPerson name])
    }
    (lldb) p $8.get(5).big()
    (method_t::big) $14 = {
      name = "setName:"
      types = 0x0000000100003f8b "v24@0:8@16"
      imp = 0x0000000100003d80 (KCObjcBuild`-[LGPerson setName:])
    }
    (lldb) 
    
    • 流程跟获取属性的大致一样
    • 获取列表的时候换成methods()方法
    • 遍历时候get()之后需要调用big()方法才能获取到元素

    为什么需要调用big(),来看源码

    image.png
    method_t的结构里方法的信息存储在struct big结构体里,所以需要调用big()方法

    总结

    通过对类的结构的探究,了解到类的方法列表属性列表协议列表都储存在bits数据结构中,后面我们会继续对类的存储探究。

    遗留问题

    本篇留下了一个问题:类的实例方法是存在methods()数据接口中的但是类方法并没有存在这里,那类方法存在哪里呢?我再下一篇《iOS底层原理探究06-类的底层原理下》中为您解答

    相关文章

      网友评论

          本文标题:iOS底层原理探究05-类的底层原理isa链&继承链&类的内存结

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