iOS 类的结构分析

183 阅读5分钟

一、什么是类

字面上看,类即Class。

typedef struct objc_class *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 *data() { 
        return bits.data();
    }
    ......
}
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
}
struct objc_object {
private:
    isa_t isa;
public:
    // ISA() assumes this is NOT a tagged pointer object
    Class ISA();
    // getIsa() allows this to be a tagged pointer object
    Class getIsa();
    ......
}

由Objective-C objc-runtime-new.h里的代码可以知道Class是一个结构体,该结构体继承自objc_object,objec_object也就是我们所说的对象。那么我们可以得出结论:类是一个结构体,也是一个对象。这也从侧面印证了万物皆对象。

二、类的结构

类的主要结构如下:

显而易见:

  • 第一个元素是一个隐藏元素(来自于父类),该属性即为isa,其指向元类;
  • 第二个元素是Classsuperclass,指向其父类;
  • 第三个元素是cache_t cache,类的相关缓存;
  • 第四个元素是class_data_bits_t bits,返回类存储的相关信息;
  • 同样我们也可以从class_rw_t *data()方法返回这些信息。

现在我们分析一下objc_clas的结构所占的内存:

  • Class isa 是一个指针,占用8字节;
  • Class superclass 同样是一个指针,占用8字节;
  • cache_t 是一个结构体,其中第一个元素是一个指针占8字节,第二、三个元素都是int32各占4字节,总共占16字节
LGPerson *person = [LGPerson alloc];
Class pClass     = object_getClass(person);

通过在在lldb中使用x/4gx 打印的地址:

0x1000023b0: 0x001d800100002389 0x0000000100b37140
0x1000023c0: 0x00000001003da260 0x0000000000000000

其中0x001d800100002389就是isa的地址,因为我们已经通过上面的计算得到了前三个元素所占用的内存,根据内存偏移,我们可以得到class_data_bits_t bits的地址为0x1000023b0 + 8 + 8 + 16 = 0x1000023b0,接着分析:

通过以上在llbd的分析,我们可以看到我们给类定义的属性存在的位置如下:

class_rw_t -> (class_ro_t *)ro -> (property_list_t)baseProperties

我们自定义的成员变量,系统给属性生成的变量(_开头命名)存储的位置如下:

class_rw_t -> (class_ro_t *)ro -> (ivar_list_t)ivars

系统给属性生成的getter、setter方法和我们自定义的实例方法存储的位置如下:

class_rw_t -> (class_ro_t *)ro -> (method_list_t)baseMethodList

我们自定义的类方法存储在元类里。

1、isa

isa是一个名为isa_t的联合体,主要属性为Class cls和 uintptr_t bits。是实例对象、类、元类的桥梁。我么可以通过(Class)(isa.bits & ISA_MASK)获取到当前的类。

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};
objc_object::ISA() 
{
    assert(!isTaggedPointer()); 
#if SUPPORT_INDEXED_ISA
    if (isa.nonpointer) {
        uintptr_t slot = isa.indexcls;
        return classForIndex((unsigned)slot);
    }
    return (Class)isa.bits;
#else
    return (Class)(isa.bits & ISA_MASK);
#endif
}

在__arm64__下isa的内存结构如下:

#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
#   define ISA_BITFIELD                                                      \
      uintptr_t nonpointer        : 1;                                       \
      uintptr_t has_assoc         : 1;                                       \
      uintptr_t has_cxx_dtor      : 1;                                       \
      uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
      uintptr_t magic             : 6;                                       \
      uintptr_t weakly_referenced : 1;                                       \
      uintptr_t deallocating      : 1;                                       \
      uintptr_t has_sidetable_rc  : 1;                                       \
      uintptr_t extra_rc          : 19

在__x86_64__下:

#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   define ISA_BITFIELD                                                        \
      uintptr_t nonpointer        : 1;                                         \
      uintptr_t has_assoc         : 1;                                         \
      uintptr_t has_cxx_dtor      : 1;                                         \
      uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
      uintptr_t magic             : 6;                                         \
      uintptr_t weakly_referenced : 1;                                         \
      uintptr_t deallocating      : 1;                                         \
      uintptr_t has_sidetable_rc  : 1;                                         \
      uintptr_t extra_rc          : 8

2、superclass

superclass指向类的父类,是继承的桥梁。

3、cache_t cache

struct cache_t {
    struct bucket_t *_buckets;
    mask_t _mask;
    mask_t _occupied;
};
typedef uint32_t mask_t;  // x86_64 & arm64 asm are less efficient with 16-bits

下一节分析

4、class_data_bits_t bits

class_data_bits_t是一个结构体,大致结构如下:

struct class_data_bits_t {
    uintptr_t bits;
private:
    bool getBit(uintptr_t bit) {
        return bits & bit;
    }
public:
    class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    ......
};

其包含一个名为class_rw_t的结构体指针,该结构体指针指向了存储了类的method、property、protocol等相关信息的内存:

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;
    const class_ro_t *ro;
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    Class firstSubclass;
    Class nextSiblingClass;
    char *demangledName;
#if SUPPORT_INDEXED_ISA
    uint32_t index;
#endif
    void setFlags(uint32_t set) {
        OSAtomicOr32Barrier(set, &flags);
    }
    void clearFlags(uint32_t clear) {
        OSAtomicXor32Barrier(clear, &flags);
    }
    // set and clear must not overlap
    void changeFlags(uint32_t set, uint32_t clear) {
        assert((set & clear) == 0);
        uint32_t oldf, newf;
        do {
            oldf = flags;
            newf = (oldf | set) & ~clear;
        } while (!OSAtomicCompareAndSwap32Barrier(oldf, newf, (volatile int32_t *)&flags));
    }
};

在class_ro_t这个结构体中

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;
    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};

tips:

类、元类创建的时机:编译期

tips:

当我们计算结构体大小的时候,不计算函数,只计算属性,函数存储的位置不一样。

tips:内存偏移

LGPerson *p1 = [LGPerson alloc];
LGPerson *p2 = [LGPerson alloc];

NSLog(@"--p1--%@----%p", p1, &p1);
NSLog(@"--p2--%@----%p", p2, &p2);

得到结果如下:
--p1--<LGPerson: 0x10108a090>----0x7ffeefbff508
--p2--<LGPerson: 0x10108e080>----0x7ffeefbff500

我们可以看出p1、p2的内存地址 和 指针地址都是不相同的。

int c[4] = {1, 2, 3, 4};
int *d = c;
NSLog(@"%p--%p--%p--%p", &c, &c[0], &c[1], &c[2]);
NSLog(@"%p--%p--%p",d,d+1,d+2);
0x7ffeefbff480--0x7ffeefbff480--0x7ffeefbff484--0x7ffeefbff488    
0x7ffeefbff480--0x7ffeefbff484--0x7ffeefbff488

for (int i = 0; i<4; i++) {
    int value1 = c[i];
    int value2 = *(d+i);
    NSLog(@"value1=%d==value=%d",value1, value2);
}
value1=1==value=1
value1=2==value=2
value1=3==value=3
value1=4==value=4

由打印的结果可以看出来 &c[0], &c[1],&c[2]的地址是递增的,间隔一个数组元素所占的内存大小;而由d,d+1,d+2可以看出来,地址指针加1就相当于内存加一个元素的大小。我们可以利用内存的偏移来反向得到内存地址中所存储的对象。

tips:内存占用大小