本文使用的 runtime 版本为 objc4-706

和对象(objc_object 结构体)一样,类(objc_class 结构体)在新的运行时中也有了很大的改变,在 objc-runtime-new.h 中可以找到它的定义:

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();
    }
    
    // 省略掉一些结构体方法。
};

可以注意到,换上 C++ 之后,objc_class 结构体直接从 objc_object 结构体继承下来了,并且注释了 isa 的存在。 比较之前的定义,第二个成员依旧是指向父类的指针,但是之后就完全不一样了,可以看到 cache 的类型变成了 cache_t,其余的信息都塞进了 bits 里。

bits 的右边可以看到好心的开发者留给后人的注释,解释了这个成员也是类似于 isa 一样,将位(bit)用作不同用途。其中最重要的就是 class_rw_t * 了,可以看到 data() 函数就是用来返回这个结构体指针的,class_rw_t 的名字表示它是类的可读可写数据。

继续在 objc-runtime-new.h 中看到 class_rw_t 结构体的定义:

struct class_rw_t {
    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;
}

可以注意到它有一个 ro 成员,是类的只读数据,这是类在编译时就确定了的东西,它的定义如下:

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

在实现中,class_rw_t 结构体的 methodspropertiesprotocols 会包含它的 ro 中对应的数据和 category 的数据,这是类在加载到运行时时做的事情,之后的文章会有详细的说明。这样,在消息发送中查找方法时,是不需要在 ro 中搜索的。

总结

大致了解了一下类在 Objective-C 运行时中的实现,具体的细节部分也还需要分析不同功能时具体分析了。