OC-runtime数据结构、类的结构

721 阅读6分钟

OC语言的本质

我们平时编写的Objective-C代码,底层实现其实都是C\C++代码

截屏2022-05-24 下午6.45.13.png Objective-C的面向对象都是基于C\C++的结构体实现的

将Objective-C代码转换为C\C++代码 用如下命令: xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件

OC对象在内存中如何布局

截屏2022-05-24 下午6.47.56.png

  • 系统分配了16个字节给NSObject对象(通过malloc_size函数获得)
  • 但NSObject对象内部(isa)只使用了8个字节的空间(64bit环境下,可以通过class_getInstanceSize函数获得)

一个对象占用16个字节

其中isa占8个 ,后面是内存补齐

int占4个,double、long占8个

2个容易混淆的函数

1.创建一个实例对象,实际上分配了多少内存?

#import <malloc/malloc.h>

malloc_size((__bridge const void *)obj);

2.创建一个实例对象,至少需要多少内存?

#import <objc/runtime.h>

class_getInstanceSize([NSObject class]);

OC对象的分类

  • instance对象(实例对象) instance对象就是通过类alloc出来的对象,每次调用alloc都会产生新的instance对象

  • class对象(类对象)

通过调用class生成的对象

  • meta-class对象(元类对象) meta-class对象和class对象的内存结构是一样的,但是用途不一样

截屏2022-05-24 下午6.54.35.png

对象的底层结构

image.png

  • superClass:实际是class类型的,它指向objc_class类型的这样一个指针
  • cache_t:实际上是装满了bucket_t数据结构的Hash表
  • class_data_bits_t:实际上是对class_rw_t的数据结构的封装
  1. class_rw_t中包含了(class_ro_t 类相关的只读信息、protocols 类分类中的协议、properties 类分类中的属性、methods 类分类中的方法)
  2. class_ro_t包含了name类名、methodList类的方法列表
  3. method_t、ivars声明的类的成员变量、类的属性、类的协议

image.png

1. objc_object

Objective-C的面向对象都是基于C/C++的数据结构——结构体实现的。
我们平时使用的所有对象都是id类型,id类型对象对应到runtime中,就是objc_object结构体。

// A pointer to an instance of a class.
typedef struct objc_object *id;

struct objc_object {
private:
    isa_t isa;
    /*...
      isa操作相关
      弱引用相关
      关联对象相关
      内存管理相关
      ...
     */
};

objc_object结构体主要包含:

  • isa_t:共用体
  • 关于isa操作相关的一些方法:通过objc_object结构体,来获取isa所指向的类对象,或者通过类对象的isa指针获取它的元类对象一些遍历的放大
  • 弱引用相关:标记一个对象是否曾经有过弱引用指针
  • 关联对象相关方法:我们为对象设置了关联属性,关于关联属性的一些相关方法也体现在objc_object结构体中
  • 内存管理相关的方法实现:MRC下经常用到的runtain,release等方法实现 ,以上均封装在objc_object结构体中

2. objc_class

Class指针用来指向一个 Objective-C 的类,它是objc_class结构体类型,所以class、meta-class底层结构都是objc_class结构体,objc_class继承自objc_object,所以它也有isa指针,它也是对象。

// An opaque type that represents an Objective-C 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 * plus custom rr/alloc flags

    class_rw_t *data() { 
        return bits.data();
    }
};

2.1 class_data_bits_t

  • class_data_bits_t主要是对class_rw_t的封装,可以通过bits & FAST_DATA_MASK获得class_rw_t

struct class_data_bits_t {
    // Values are the FAST_ flags above.
    uintptr_t bits;
public:
    class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
};

2.1.1 class_rw_t

  • class_rw_t代表了类相关的读写信息,它是对class_ro_t的封装;

  • class_rw_t中主要存储着类的方法列表、属性列表、协议列表等;

  • class_rw_t里面的methodspropertiesprotocols都继承于list_array_tt二维数组,是可读可写的,包含了类的初始内容、分类的内容。

image.png


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


2.1.2 class_ro_t

  • class_ro_t代表了类相关的只读信息;

  • class_ro_t中主要存储着类的成员变量列表、类名等;

  • class_ro_t里面的baseMethodListbaseProtocolsivarsbaseProperties是一维数组,是只读的,包含了类的初始内容;

  • 一开始类的信息都存放在class_ro_t里,当程序运行时,经过一系列的函数调用栈,在realizeClass()函数中,将class_ro_t里的东西和分类的东西合并起来放到class_rw_t里,并让bits指向class_rw_t

image.png


struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;  // instance对象占用的内存空间
#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;
    }
};

2.2 cache_t

  • 是用于快速查找方法执行函数的一个结构(当我们调用一个方法时,如果有缓存,我们就不需要去方法列表中遍历了,可以提高方法调用速度)
  • 是可增量扩展的哈希表结构(当结构存储量增大的过程中, cache_t会增量扩大它的内存空间来支持更多的缓存,用哈希表实现这个数据结构,是为了提高查找效率)
  • cache_t数据结构是计算机局部性原理的最佳应用(计算机局部性原理:在一般调用方法时,有几个方法调用频次较高,把他们放到方法缓存中,下次的命中率就会更高) cache_t具体数据结构说明:

image.png

可以理解为是数组来实现的

  • 每个对象都是bucket_t这样的一个结构体, bucket_t有两个主要成员变量,key和IMP key对应OC中的selector,在调用方法时是个选择器SEL
  • IMP理解为无类型的函数指针,可以通过方法选择器的名称key来寻找这个方法的具体实现IMP

假如现在有个key,可以通过哈希查找算法来定位当前key所对应的bucket_t位于数组当中哪个位置,然后从这个位置中提取出bucket_t中的IMP


struct cache_t {
    struct bucket_t *_buckets;  // 哈希表
    mask_t _mask;               // 哈希表的长度 - 1
    mask_t _occupied;           // 已经缓存的方法数量
};

struct bucket_t {
private:
    cache_key_t _key;  // SEL
    IMP _imp;          // IMP 函数的内存地址
};

3. isa 指针

它是C++中的共用体,在OC中名称是isa_t 不论是64位架构上(或者32位架构)上面,共用体是64个(或者32个) 0或者1的数字 (按大多数64位分析)

image.png

分两种类型(isa指针是什么含义的时候):

  • 指针型isa: 64位的0或者1的整体内容代表所指向的Class的地址,也就是可以通过isa的内容来获得类对象的地址
  • 非指针型isa: isa的值得部分代表Class的地址,之所以这样是因为我们在寻址过程中,只有三四十位数就可以保证我们寻找到所有Class地址了,多出来的位可以用来存储其他相关内容,来达到节省内存的目的

isa的指向: 对象的isa指针,指向类对象 类对象的isa指针,指向元类对象

4. method_t

method_t是对函数四要素(名称、返回值、参数、函数体)的封装
函数四要素决定了函数的唯一性



struct method_t {
    SEL name;  // 方法名
    const char *types;  // 编码(返回值类型、参数类型)
    IMP imp;   // 方法的地址/实现
};

  • SEL 又称“选择器”,它是一个指向方法的selector的指针,代表方法/函数名;
  • IMP 是指向方法实现的函数指针;
  • 我们调用方法,实际上就是根据方法 SEL 查找 IMP;
  • method_t实际上相当于在 SEL 和 IMP 之间做了一个映射。

method_t是个结构体,主要有三个数据类型

  1. name 函数名称
  2. types 函数返回值和参数的集合
  3. imp 无类型的函数指针,对应着函数体

参考 当面试官问Runtime时,想听到的答案是什么?