iOS - 类结构分析

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

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 _buckets; // 是一个结构体指针类型,占8字节
    explicit_atomic _mask; //是mask_t 类型,而 mask_t 是 unsigned int 的别名,占4字节
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    explicit_atomic _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 - 类结构分析)