iOS底层原理06:类的底层原理探索(1)

2,598 阅读6分钟

前言

iOS界比较流行的一句话:万物皆对象!为什么会有这么一个说法呢?难道类(class)也是对象(object)?

本文所采用的源码为苹果开源的最新 objc4-781 版本。

1、类的本质是什么?

iOS底层原理04:isa底层原理分析上文章中,我们使用clang编译过main.m文件,并且从源码中得知:

  • 1.对象的本质是个结构体
  • 2.NSObject_IVARS本质是class类型的isa
  • 3.objc_class是一个结构体。在iOS中,所有的Class都是以 objc_class 为模板创建的。
struct Person_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    NSString *_name;
};

struct NSObject_IMPL {
    Class isa;
};

typedef struct objc_class *Class;

于是在objc4-781源码中搜索objc_class的定义,源码中对objc_class的定义有两个版本。

  • 旧版的定义在runtime.h中,已经废除。
  • 新版的定义在objc-runtime-new.h中。

从新版的定义中,可以看到 objc_class 结构体类型是继承objc_object的。

总结

类的本质objc_class 结构类型的指针,是继承objc_object,所有的Class都是以 objc_class 为模板创建的。

2、objc_class 和 objc_object的关系?

我们在objc4源码中搜索objc_object {,源码中对objc_object的定义有两个版本。

  • 定义在objc.h中。
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
  • 定义在objc-private.h中。
struct objc_object {
private:
    isa_t isa;

public:
    // ISA() assumes this is NOT a tagged pointer object
    Class ISA();
     ...
     }

通过上述源码,可以总结出几点:

  • 1、objc_class 继承自objc_object,其中objc_object也是一个结构体,且有一个isa属性,所以objc_class也拥有了isa属性
  • 2、NSObject的定义是Class类型的isa,其中Class的底层编码来自 objc_class类型,所以NSObject也拥有了isa属性。
  • 3、NSObject 是一个类,用它初始化一个实例对象objcobjc 也拥有isa属性,主要是因为 NSObject类 从objc_class继承过来的,而objc_class继承自objc_objectobjc_objectisa属性。
  • 4、所有以 objc_object为模板 创建的对象,都有isa属性。
  • 5、所有以objc_class为模板,创建的,都有isa属性

万物皆对象,万物皆来源于objc_object

objc_objectobjc_classisaobjectNSObject等的整体的关系,如下图所示

3、什么是地址平移?

1、普通指针

我们先来看一段代码:

void normalPointer(){
    int a = 10;
    int b = 10;
    MyNSLog(@"a的地址是:%p   a的值是:%d",&a,a);
    MyNSLog(@"b的地址是:%p   b的值是:%d",&b,b);
}

打印结果:

a的地址是:0x7ffeefbff590   a的值是:10
b的地址是:0x7ffeefbff594   b的值是:10
  • a、b地址不同,相差4个字节。这个取决于a、b的类型(int),a、b的值相同,说明他们都指向10这个常量,这也是值拷贝。
  • 地址指向如下图所示:

2、对象指针

接着看一段代码:

void objectPointer(){
       Person *p1 = [Person alloc]; 
       Person *p2 = [Person alloc];
       MyNSLog(@"%p -- %p", p1, &p1);
       MyNSLog(@"%p -- %p", p2, &p2);
}

打印结果:

0x101859b70 -- 0x7ffeefbff588
0x101858440 -- 0x7ffeefbff590
  • p1、p2 是指针,p1 是 指向 [Person alloc]创建的空间地址,即内存地址,p2 也是一样。

  • &p1、&p2是 指向 p1、p2对象指针的地址,这个指针 就是 二级指针

  • 来个图说明一下:

3、数组指针

继续看一段代码:

void arrayPointer(){
    int c[4] = {1, 2, 3, 4};
    int *d = c;
    MyNSLog(@"%p -- %p - %p", &c, &c[0], &c[1]);
    MyNSLog(@"%p -- %p - %p", d, d+1, d+2);
}

打印结果:

0x7ffeefbff580 -- 0x7ffeefbff580 - 0x7ffeefbff584
0x7ffeefbff580 -- 0x7ffeefbff584 - 0x7ffeefbff588
  • &c 和 &c[0] 都是取数组的 首地址,&c[0] 和 &c[1]相差4个字节,取决于它们的数据类型
  • *d = c,即将c的首地址赋值给d,d+1,即在内存中取下一个地址,地址平移的字节数 = 偏移量 * 数据类型字节数,这就是地址平移
  • 来个图说明一下:

地址平移的作用

  • 我们探索类信息的时候,事先我们并不清楚类的结构是什么样的,但是我们可以通过类得到一个首地址,然后通过地址平移去获取里面所有的值。

4、类的结构分析

1、类的结构有什么?

根据上文在objc4-781源码中搜索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
    
    class_rw_t *data() const {
        return bits.data();
    }
      ...   
      以下部分定义了方法,此处省略
}
  • isa:继承自objc_object的isa,占 8字节
  • superclassClass类型,Class是由objc_object定义的,是一个指针,占8字节
  • cache:只知道是cache_t类型,至于里面是什么,下文揭晓。
  • bits:看得出是class_data_bits_t,至于里面是什么,下文揭晓。

2、cache_t探索

查看cache_t源码(忽略静态属性和方法),主要的结构如下:

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 是 unsigned int 类型,占4字节

#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    explicit_atomic<uintptr_t> _maskAndBuckets;
    mask_t _mask_unused;
    
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    // _maskAndBuckets stores the mask shift in the low 4 bits, and
    // the buckets pointer in the remainder of the value. The mask
    // shift is the value where (0xffff >> shift) produces the correct
    // mask. This is equal to 16 - log2(cache_size).
    explicit_atomic<uintptr_t> _maskAndBuckets; //uintptr_t 是 unsigned long 类型,占8字节
    mask_t _mask_unused; //mask_t 是 unsigned int 类型,占4字节
    
#if __LP64__
    uint16_t _flags; //uint16_t 是 unsigned short 类型,占2字节
#endif
    uint16_t _occupied; //uint16_t 是 unsigned short 类型,占2字节
}
  • 计算cache_t内存大小:
  • if流程:
    • buckets 类型是struct bucket_t *,是结构体指针类型,占8字节。
    • _maskmask_t 类型,而 mask_tunsigned int 类型,占4字节。
  • else流程:
    • _maskAndBuckets 类型是uintptr_t,而uintptr_tunsigned long 类型,占8字节。
    • _maskmask_t 类型,而 mask_tunsigned int 类型,占4字节。
  • if - else不管执行哪一个,这一部分的内存大小 = 8 + 4 = 12字节。
  • _flagsuint16_t类型,uint16_tunsigned short 类型,占2字节
  • _occupied也是是uint16_t类型,占2字节

小结:

cache_t的内存大小 = 12 + 2 + 2 = 16字节。

3、bits探索

通过上述的探索,我们知道类的属性isa8字节,superclass8字节,cache16字节,因此我们想要获取bits的内容,那么类的首地址需要平移 8 + 8 + 16 = 32字节。

接下来我们结合代码,探索一番。

  • 首先申明个p1对象,打个断点,通过lldb命令调试
  • 执行 p/x Person.class获取首地址:
(lldb) p/x Person.class
(Class) $0 = 0x00000001000032b8 Person
  • 将首地址平移32字节,获取bits的地址,32的16进制是0x20,执行p (class_data_bits_t *) 0x00000001000032d8
 p (class_data_bits_t *) 0x00000001000032d8
(class_data_bits_t *) $1 = 0x00000001000032d8
  • 通过bits地址获取data数据,执行 p $1->data(),获取到了class_rw_t类型的数据
class_rw_t *data() const {
        return bits.data();
    }
  • 打印结果:
p $1->data()
(class_rw_t *) $2 = 0x00000001006bf040
  • 执行p *$2,打印数据中的信息
(lldb) p *$2
(class_rw_t) $3 = {
  flags = 2148007936
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = 4294979904
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}

  • 通过上面的步骤,我们成功获取了bits的地址,从而获取到了data数据,那么class_rw_t里面又是什么呢?我们接着往下探索。

4、class_rw_t探索

点击查看class_rw_t源码,发现有很多内容,有很多不懂的东西,继续往下看代码,终于看到了写熟悉的字眼...

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t witness;
#if SUPPORT_INDEXED_ISA
    uint16_t index;
#endif
   ...此处省略代码
   
   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 *>()->methods;
        } else {
            return method_array_t{v.get<const class_ro_t *>()->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 *>()->properties;
        } else {
            return property_array_t{v.get<const class_ro_t *>()->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 *>()->protocols;
        } else {
            return protocol_array_t{v.get<const class_ro_t *>()->baseProtocols};
        }
    }
}
  • 从源码中,我们可以知道class_rw_t是一个结构体,存储了方法列表,属性列表,协议列表等等。

  • 对于类的底层探索,就先到这里了,那么如何去获取类的属性,方法等,在下一篇文章继续探索。