美文网首页
iOS - 类结构分析

iOS - 类结构分析

作者: malgee | 来源:发表于2021-05-12 14:42 被阅读0次

    我们都知道,一个类可以创建多个不同实例对象,类自己也是对象(类对象),那么类在内存中会存在几份呢?看下面结果

    Class class1 = [MGPerson alloc].class;
    Class class2 = [MGPerson alloc].class;
    Class class3 = [MGPerson class];
    Class class4 = object_getClass([MGPerson alloc]);
    

    得出的结果是 class1class2class3class4的地址一样

    image.png

    也就是类对象并不像实例对象一样在内存中有多份存在,只占用一份内存。

    总结:类(类对象)在内存中只有一份存在;

    类的内存地址分析

    通过控制台输出类的地址是 0x0000000100002330

    (lldb) p/x MGPerson.class
    (Class) $0 = 0x0000000100002330 MGPerson
    

    然而当我们用类的isa & 0x7ffffffffff8得到地址0x0000000100002308, po输出也是MGPerson, 可以通过上一节isa 和 类的关系查看isa分析

    image.png

    看到这里会发现和前面的分析结论类在内存中只有一份 相矛盾,真的是这样的吗?答案是否定的,看到的0x0000000100002308并不是类的内存地址,而是元类, 是MGPerson中的isa指向的元类。

    元类的说明:
    • 元类是系统给的,其定义和创建都是由编译器完成,在这个过程中,类的归属来自元类。
    • 元类本身没有名称的, 由于与类相关联,所以使用了同类名一样 的名称。

    接着分析MGPerson类对象的元类的地址,输出内存地址为:

    (lldb) x/4gx 0x0000000100002308
    0x100002308: 0x00007fff8e0c50f0 0x00007fff8e0c50f0
    0x100002318: 0x00000001007040c0 0x0001e03500000007
    

    继续元类的isa & 0x7ffffffffff8得到地址0x00007fff8e0c50f0, po输出是NSObject

    image.png

    元类的isa指向根元类, 那么根元类isa指向谁? 继续输出 0x00007fff8e0c50f0指向的地址

    
    (lldb) x/4gx 0x00007fff8e0c50f0
    0x7fff8e0c50f0: 0x00007fff8e0c50f0 0x00007fff8e0c5118
    0x7fff8e0c5100: 0x00000001005153c0 0x0004e03100000007
    
    image.png

    可以发现根元类isa指向的还是自己,即 0x00007fff8e0c50f0。

    至此可以得出实例对象元类根元类的关系图如下:

    image.png

    总结:

    • 实例对象(Instance of Subclass) 的 isa 指向类(class)

    • 类对象(class) isa 指向 元类(Meta class)

    • 元类(Meta class)的isa 指向 根元类(Root metal class)

    • 根元类(Root metal class) 的isa 指向自己,形成闭环,这里的根元类就是NSObject

    类(Class)

    我们定义的对象都是继承于NSObject对象,可以通过isa 和 类的关系中找到类的结构体(objc_class)定义。

    struct objc_class : objc_object {
        // Class ISA;   // 8
        Class superclass;   // 8
        cache_t cache;      // 16       // formerly cache pointer and vtable
        class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    
        class_rw_t *data() const {
            return bits.data();
        }
        void setData(class_rw_t *newData) {
            bits.setData(newData);
        }
    
        void setInfo(uint32_t set) {
            ASSERT(isFuture()  ||  isRealized());
            data()->setFlags(set);
        }
    
        void clearInfo(uint32_t clear) {
            ASSERT(isFuture()  ||  isRealized());
            data()->clearFlags(clear);
        }
    
        // set and clear must not overlap
        void changeInfo(uint32_t set, uint32_t clear) {
            ASSERT(isFuture()  ||  isRealized());
            ASSERT((set & clear) == 0);
            data()->changeFlags(set, clear);
        }
    
        ...
    }
    

    可以看出 objc_class 继承于 objc_object, 发现有一个isa变量,可以得出所有的对象都有一个isa指针。

    struct objc_object {
    private:
        isa_t isa;
    
    public:
    
        // ISA() assumes this is NOT a tagged pointer object
        Class ISA();
    
        // rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA
        Class rawISA();
    
        // getIsa() allows this to be a tagged pointer object
        Class getIsa();
        
        uintptr_t isaBits() const;
      ... 
    }
    

    总结:

    • 所有的对象, 元类都有isa变量
    • 所有的对象都继承于objc_object

    类结构分析

    struct objc_class : objc_object {
        // Class ISA;   // 8
        Class superclass;   // 8
        cache_t cache;      // 16       // formerly cache pointer and vtable
        class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    
      // 获取bits存放的数据信息
        class_rw_t *data() const {
            return bits.data();
        }
        ...
    }
    

    通过上面分析可以看出类(class)中有4个属性分别是:

    • isa: 继承自objc_objectisa,占用8个字节;
    • superclass: Class类型, 也是objc_class结构体,是一个结构体指针,占用8个字节;
    • cache: 存放缓存的指针,以及缓存的函数,占用16字节;
    • bits: 类型为 class_data_bits_t ,存储了类中更详细的信息(例如:属性,方法,协议);

    【cache_t类型】内存大小 16字节

    struct cache_t {
    #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
        explicit_atomic<struct bucket_t *> _buckets; // 是一个结构体指针类型,占8字节
        explicit_atomic<mask_t> _mask; //是mask_t 类型,而 mask_t 是 unsigned int 的别名,占4字节
    #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
        explicit_atomic<uintptr_t> _maskAndBuckets; //是指针,占8字节
        mask_t _mask_unused; //是mask_t 类型,而 mask_t 是 uint32_t 类型定义的别名,占4字节
       
      ...
    
    #if __LP64__
        uint16_t _flags;  //是uint16_t类型,uint16_t是 unsigned short 的别名,占 2个字节
    #endif
        uint16_t _occupied; //是uint16_t类型,uint16_t是 unsigned short 的别名,占 2个字节
    
       ....
    }
    

    class_data_bits_t结构分析

    通过上面分析类的结构可知 类内存地址(首地址)平移32字节(即十六进制0x20)即可查找到 bits

    image.png

    步骤解析:

      1. 获取类的首地址 0x00000001000013d8
      1. 通过首地址偏移32字节(即0x20)得到 bits的内存地址
      1. 强转成 class_data_bits_t类型, 读取bits.data()存放的数据信息,即$3;
      1. 打印输出 $3 的内容 (class_rw_t) $4, 类型是class_rw_t,也是一个结构体类型;

    class_rw_t 结构

    在64位架构CPU下,bits 的第3到第46字节存储class_rw_t 。class_rw_t 中存储 flags 、witness、firstSubclass、nextSiblingClass, methods, properties, protocols 以及class_rw_ext_t

    struct class_rw_ext_t {
        const class_ro_t *ro;
        method_array_t methods;
        property_array_t properties;
        protocol_array_t protocols;
        char *demangledName;
        uint32_t version;
    };
    

    class_rw_ext_t 中存储着class_ro_tmethods (方法列表)properties (属性列表)protocols (协议列表)等信息。
    class_ro_t 中也存储了baseMethodList (方法列表)baseProperties (属性列表)baseProtocols (协议列表) 以及 实例变量、类的名称、大小 等等信息。

    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;
    };
    

    通过源码获取属性列表、方法列表、协议列表等

    image.png
    • 通过获取属性列表property_array_t可以拿到属性
    image.png
    image.png

    注:p *($6 + 1)p *($6 + 2)可以获得属性列表的中的下一个数据

    或者通过$7.get()获取各个属性

    image.png

    注意:properties()只能获取到属性列表, 如果成员变量并不在这里面存放着,而是存放在 ivars成员变量列表中。

    • 通过函数methods()获取 method_array_t获取方法列表
    image.png

    步骤解析:

    • p $4.methods() 获取方法列表list, 即得到的$6
    • p *$6拿到方法列表中的第一个方法,即 ‘sayHello’
    • p $7.get(1)p $7.get(2)等是获取方法列表中的其他方法。

    相关文章

      网友评论

          本文标题:iOS - 类结构分析

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