深入理解iOS Runtime(一):数据结构

253 阅读3分钟

Runtime是一个用C、C++、汇编编写的运行时库,包含了很多C语言的API,封装了很多动态性相关的函数;Objective-C是一门动态运行时语言,允许很多操作推迟到程序运行时再进行。OC的动态性就是由Runtime来支撑和实现的,Rumtime就是它的核心;我们平时编写的OC代码,底层都是转换成了Runtime API进行调用。

由此可见Runtime的重要性,这一篇主要讲下Runtime的数据结构,也为后续分析Runtime的其他特性提供基础。

Runtime数据结构.png

image.png

isa 指针

struct objc_object {
    Class isa;  // 在 arm64 架构之前
};

struct objc_object {
private:
    isa_t isa;  // 在 arm64 架构开始
};

union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;

#if SUPPORT_PACKED_ISA

    // extra_rc must be the MSB-most field (so it matches carry/overflow flags)
    // nonpointer must be the LSB (fixme or get rid of it)
    // shiftcls must occupy the same bits that a real class pointer would
    // bits + RC_ONE is equivalent to extra_rc + 1
    // RC_HALF is the high bit of extra_rc (i.e. half of its range)

    // future expansion:
    // uintptr_t fast_rr : 1;     // no r/r overrides
    // uintptr_t lock : 2;        // lock for atomic property, @synch
    // uintptr_t extraBytes : 1;  // allocated with extra bytes

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL  // 用来取出 Class、Meta-Class 对象的内存地址
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    struct {
        uintptr_t nonpointer        : 1;  // 0:代表普通的指针,存储着 Class、Meta-Class 对象的内存地址
                                          // 1:代表优化过,使用位域存储更多的信息
        uintptr_t has_assoc         : 1;  // 是否有设置过关联对象,如果没有,释放时会更快
        uintptr_t has_cxx_dtor      : 1;  // 是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快
        uintptr_t shiftcls          : 33; // 存储着 Class、Meta-Class 对象的内存地址信息
        uintptr_t magic             : 6;  // 用于在调试时分辨对象是否未完成初始化
        uintptr_t weakly_referenced : 1;  // 是否有被弱引用指向过,如果没有,释放时会更快
        uintptr_t deallocating      : 1;  // 对象是否正在释放
        uintptr_t has_sidetable_rc  : 1;  // 如果为1,代表引用计数过大无法存储在 isa 中,那么超出的引用计数会存储在一个叫 SideTable 结构体的 RefCountMap(引用计数表)哈希表中
        uintptr_t extra_rc          : 19; // 里面存储的值是引用计数 retainCount - 1
#       define RC_ONE   (1ULL<<45)
#       define RC_HALF  (1ULL<<18)
    };
};

isa指针用来维护对象和类之间的关系,并确保对象和类能够通过isa指针找到对应的方法、实例变量、属性、协议等;在arm64架构之前,isa就是一个普通的指针,直接指向objc_class,存储着ClassMeta-Class对象的内存地址。instance对象的isa指向class对象,class对象的isa指向meta-class对象;从 arm64 架构开始,对isa进行了优化,变成了一个共用体(union)结构,不仅仅承担指针的作用,还使用位域来存储更多的信息。

isa与superclass指针指向

image.png 由上图可见

  • 实例对象isa指向类对象类对象isa指向元类对象;
  • 元类对象isa指向根元类对象根元类对象isa指向自己
  • 类对象元类对象superclass指向各自的父类,特别注意的一点是根元类对象superclass指向根类对象
类对象(class)与元类对象(meta-class)
  • classmeta-class底层结构都是objc_class结构体,objc_class继承自objc_object,所以它也有isa指针,所以它也是对象;
  • class中存储着实例方法、成员变量、属性、协议等信息, meta-class中存储着类方法等信息;
  • isa指针和superclass指针的指向(如上图);
  • 基类的meta-classsuperclass指向基类的class,决定了一个性质:当我们调用一个类方法,会通过classisa指针找到meta-class,在meta-class中查找有无该类方法,如果没有,再通过meta-classsuperclass指针逐级查找父meta-class,一直找到基类的meta-class如果还没找到该类方法的话,就会去找基类的class中同名的实例方法的实现。

objc_object

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

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

objc_class

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

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();
    }
};
class_data_bits_t
  • class_data_bits_t主要是对class_rw_t的封装,可以通过bits & FAST_DATA_MASK获得class_rw_t
  • class_rw_t代表了类相关的读写信息,它是对class_ro_t的封装;
  • class_rw_t中主要存储着类的方法列表、属性列表、协议列表等;
  • class_rw_t里面的methodspropertiesprotocols都继承于list_array_tt二维数组,是可读可写的,包含了类的初始内容、分类的内容。
struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    // 请注意,Symbolication 知道此结构的布局。
    
    // 在上篇的测试代码中:flags 打印看到是 2148007936
    // 转为二进制的话是只有 31 位和 19 位是 1,其它位全部都是 0,对应于:
    // class has started realizing but not yet completed it
    // #define RW_REALIZING (1<<19)
    // class_t->data is class_rw_t, not class_ro_t
    // #define RW_REALIZED (1<<31)
    
    uint32_t flags;
    
    //(控制台打印值为 1)
    uint16_t witness;
    
#if SUPPORT_INDEXED_ISA // isa 中保存 indexcls,大概是 watchOS 下才会用到
    uint16_t index;
#endif

    // std::atomic<uintptr_t>
    // 原子性 unsigned long
    
    // 执行如下命令,会打印 error:
    // (lldb) p $2->ro_or_rw_ext
    // error: no member named 'ro_or_rw_ext' in 'class_rw_t'
    
    // 在编译时会根据类定义生成类的 class_ro_t 数据,其中包含方法列表、属性列表、成员变量列表等等内容
    
    // ro_or_rw_ext 会有两种情况:
    // 1): 编译时值是 class_ro_t *
    // 2): 编译后类实现完成后值是 class_rw_ext_t *,而编译时的 class_ro_t * 作为 class_rw_ext_t 的 const class_ro_t *ro 成员变量保存
    explicit_atomic<uintptr_t> ro_or_rw_ext; // 变量名字对应与 class_ro_t or(或) class_rw_ext_t

    // 当前所属类的第一个子类
    // 测试时,定义了一个继承自 NSObject 的类,
    // 控制台打印看到它的 firstSubclass 是 nil
    Class firstSubclass;
    
    // 姊妹类、兄弟类
    // 测试时,定义了一个继承自 NSObject 的类,
    // 控制台打印看到 nextSiblingClass 是 NSUUID(好奇怪)
    
    // firstSubclass 和 nextSiblingClass 有超级重要的作用,后面会展开
    Class nextSiblingClass;
    ...
};
  • class_ro_t代表了类相关的只读信息
  • class_ro_t中主要存储着类的成员变量列表、类名等;
  • class_ro_t里面的baseMethodListbaseProtocolsivarsbaseProperties是一维数组,是只读的,包含了类的初始内容;
  • 一开始类的信息都存放在class_ro_t里,当程序运行时,经过一系列的函数调用栈,在realizeClass()函数中,将class_ro_t里的东西和分类的东西合并起来放到class_rw_t里,并让bits指向class_rw_t
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;
    }
};
  • class_rw_ext_t只有在动态添加属性、方法或协议时创建
  • 与当前类存在类别

image.png

struct class_rw_ext_t {
    DECLARE_AUTHED_PTR_TEMPLATE(class_ro_t)
    class_ro_t_authed_ptr<const class_ro_t> ro;
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    char *demangledName;
    uint32_t version;
};
  • method_array_tmethod_list_t

image.png