[iOS底层]-类的结构分析

2020/9/14 posted in  iOS底层 总阅读量

类的本质

类在OC中到底是什么,在底层又是如何实现的呢?今天我们就来探讨一下,首先打开objc4底层源码,我们找到Class的源码。

typedef struct objc_class *Class;

Class是一个objc_class结构体指针, 我们跟进到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;    // class_rw_t * plus custom rr/alloc flags
    
    ...
}

// objc_object结构体
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
  • objc_class拥有superclass, cachebits三个属性。
  • objc_class继承自objc_object结构体。
  • isaobjc_object的属性,同时objc_class继承objc_object,所以objc_class也有isa

在源码中,找到NSObject的定义如下:

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}

@end

所以objc_object、objc_class、NSObject、isa的关系如下图所示。

类的属性

我们先创建一个LJPerson

它有两个成员变量hobby和objc,两个属性nickNamename

类的属性存放在哪里?

根据objc_class结构体的定义,其中bits很可能就是用来存放方法和属性的,果然,bits.data()返回的class_rw_t结构体里面可以找到获取方法和属性的方法。

现在我们考虑的就是如何去获取bits

如何获取bits?

结构体在内存中是连续存储的,根据首地址进行指针偏移,可以获取到结构体的属性地址。要想找到bits,我们需要先计算需要偏移多少值才能找到。

  • isa是一个结构体指针,占8位
  • superclass也是一个结构体指针,也占8位
  • cache是一个cache_t的结构体,结构体所占内存大小,由内部属性决定。只要计算出cache的内存大小,就可以知道偏移量了。

计算cache结构体的内存大小
进入cache_t结构体的定义,去掉static的属性(static的属性内存不存在结构体中,不用计算内存大小),还有如下属性:

struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    // <>:表示泛型,类型为struct bucket_t *,是一个指针,占8位
    explicit_atomic<struct bucket_t *> _buckets; 
    // mask_t 是 uint32_t的别称,占4位
    explicit_atomic<mask_t> _mask;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    // uintptr_t 是 unsigned long 的别称,占8位
    explicit_atomic<uintptr_t> _maskAndBuckets;
    // mask_t 是 uint32_t的别称,占4位
    mask_t _mask_unused;

// 因为是if判断,所以一共占12位 

#if __LP64__
    // uint16_t 是unsigned short的别名,占2位
    uint16_t _flags; 
#endif
    // uint16_t 是unsigned short的别名,占2位
    uint16_t _occupied;
};

所以cache_t的内存大小是12+2+2=16

扩展:类型占用大小查询

最终得到偏移量为8+8+16=32

通过lldb获取bits信息

我们计算出偏移量为32,现在通过首地址偏移来获取bits

获取属性信息


通过打印调试,LJPerson的两个属性确实找到了,但是hobby和objc两个成员变量没有找到,他们又存储在哪里?

类的成员变量

  • class_rw_t通过调用ro()方法,可以找到ivars的地址,ivars保存有成员变量信息。
  • 不带下划线的成员变量是我们手动创建的,带下划线的是我们声明属性后系统自动创建的。

类的方法

LJPerson类中增加一个实例方法instanceMethod和类方法classMethod

实例方法

和获取属性一样,我们通过调用bits获取到方法列表method_list_t

打印方法列表数组

  • method_list_t存储了instanceMethod实例方法
  • method_list_t同时也存储属性的setget方法,以及C++的析构方法。
  • 没有找到类方法classMethod

类方法

我们知道类的isa指向的是元类,类的bits保存的是实例方法信息,元类是否保存的就是类方法的信息呢?我们接下来就验证一下。

  • 在元类的方法列表method_list_t中找到了类方法classMethod的存储信息
  • 方法的归属:
    • 实例方法存储在的方法列表中。
    • 类方法存储在元类的方法类别中。

类结构图