OC类的底层结构objc_class

objc_class源码解析

类是对象的抽象,而对象是类的具体实例。在OC中类本身也是一个对象,叫类对象,类对象的类叫元类。我们对象方法、属性、实成员变量等都是存储在类结构中。而类的底层结构是一个结构体。其底层结构体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();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }
......此处省略1000多行代码
}

由于其结构体代码较多,这里只展示关键部分。其中可以看出objc_class继承自objc_object结构体(有关结构objc_object可以参考OC对象的本质-objc_object),这证明了类本身也是对象得事实,既然继承自objc_object,那么自然也是有isa指针(点击了解关于isa与类及元类的关系)。另外我们可以看出objc_class还有几个重要属性superclass、cache和bits。这其中bits跟class_rw_t看似有着重要的关系。那他们分别是什么?有什么作用呢?而且,作为类的结构体,objc_class里面没有看到方法列表、属性列表等基本的类结构信息,那这些信息到底存在哪里呢?接下来继续分析。

Class superclass

这里的superclass其实就是指向父类,表示类的继承关系。OC类的继承关系可以参考下图:

Superclass.jpg

在OC中Root class就是NSObject。根类Root class的superclass指针指向nil。

cache_t

cache属性是cache_t类型的。有名字可知是跟缓存有关系的,例如方法缓存等。cache_t是个结构体,其数据结构可以简化成如下代码(点击了解cache_t详细信息):

struct cache_t {
    explicit_atomic _buckets;
    explicit_atomic _mask;
#if __LP64__
    uint16_t _flags;
#endif
    uint16_t _occupied;

public:
    static bucket_t *emptyBuckets();
    
    struct bucket_t *buckets();
    mask_t mask();
    mask_t occupied();
};

class_data_bits_t

class_data_bits_t作为属性bits的类型,也是个结构体,其数据结构如下:

struct class_data_bits_t {
    friend objc_class;//这里声明objc_class为class_data_bits_t的友元类,使得objc_class可以访问class_data_bits_t中的私有方法
    uintptr_t bits;
    ......

从源码中可以看到,class_data_bits_t有一个属性uintptr_t bits,bits的类型是uintptr_t类型数据,那么uintptr_t是什么数据结构呢?以下是通过查看一些资料得到的解释:

是无符号整数类型,能够存储指针。这通常意味着它与指针的大小相同。 它可选地在C ++ 11和更高版本的标准中定义。 想要一个可以保存体系结构指针类型的整数类型的常见原因是对指针执行特定于整数的操作,或者通过将指针提供为整数“句柄”来模糊指针的类型。

这里面其实利用位域存储数据,简单来说因为这个bits跟指针大小相同,在iOS中指针大小是8个字节,也就是64位,但是通常单一数据无法用满这64位的,会有很多空位,造成空间浪费。通过位运算,把不同的数据按不同的位置存进这64位内存空间的不同位置里,这样就可以提高内存利用率。这里的bits其实就是这样的,他不只存储class_rw_t结构体指针,还存储其他的信息。
看到这里我们获取会有一些疑惑,那就是类结构里面我们没有看到方法列表、属性列表甚至成员列表等,那这些东西存在哪里呢?由前面分析知道bits里有个class_rw_t指针,而且objc_class里面的data和getData方法都涉及到class_rw_t的读写:

    class_rw_t *data() const {
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }

因此我们猜测class_rw_t里面应该会有类相关的信息。接下来对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
    explicit_atomic ro_or_rw_ext;
    Class firstSubclass;
    Class nextSiblingClass;
private:
    using ro_or_rw_ext_t = objc::PointerUnion;
    const ro_or_rw_ext_t get_ro_or_rwe() const {
        return ro_or_rw_ext_t{ro_or_rw_ext};
    }
    void set_ro_or_rwe(const class_ro_t *ro) {
        ro_or_rw_ext_t{ro}.storeAt(ro_or_rw_ext, memory_order_relaxed);
    }
    void set_ro_or_rwe(class_rw_ext_t *rwe, const class_ro_t *ro) {
        // the release barrier is so that the class_rw_ext_t::ro initialization
        // is visible to lockless readers
        rwe->ro = ro;
        ro_or_rw_ext_t{rwe}.storeAt(ro_or_rw_ext, memory_order_release);
    }
    class_rw_ext_t *extAlloc(const class_ro_t *ro, bool deep = false);
public:
    void setFlags(uint32_t set)
    {
        __c11_atomic_fetch_or((_Atomic(uint32_t) *)&flags, set, __ATOMIC_RELAXED);
    }
    void clearFlags(uint32_t clear) 
    {
        __c11_atomic_fetch_and((_Atomic(uint32_t) *)&flags, ~clear, __ATOMIC_RELAXED);
    }
    // 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_rw_ext_t *ext() const {
        return get_ro_or_rwe().dyn_cast();
    }
    class_rw_ext_t *extAllocIfNeeded() {
        auto v = get_ro_or_rwe();
        if (fastpath(v.is())) {
            return v.get();
        } else {
            return extAlloc(v.get());
        }
    }
    class_rw_ext_t *deepCopy(const class_ro_t *ro) {
        return extAlloc(ro, true);
    }
    const class_ro_t *ro() const {
        auto v = get_ro_or_rwe();
        if (slowpath(v.is())) {
            return v.get()->ro;
        }
        return v.get();
    }
    void set_ro(const class_ro_t *ro) {
        auto v = get_ro_or_rwe();
        if (v.is()) {
            v.get()->ro = ro;
        } else {
            set_ro_or_rwe(ro);
        }
    }
    const method_array_t methods() const {
        auto v = get_ro_or_rwe();
        if (v.is()) {
            return v.get()->methods;
        } else {
            return method_array_t{v.get()->baseMethods()};
        }
    }
    const property_array_t properties() const {
        auto v = get_ro_or_rwe();
        if (v.is()) {
            return v.get()->properties;
        } else {
            return property_array_t{v.get()->baseProperties};
        }
    }
    const protocol_array_t protocols() const {
        auto v = get_ro_or_rwe();
        if (v.is()) {
            return v.get()->protocols;
        } else {
            return protocol_array_t{v.get()->baseProtocols};
        }
    }
};

从这个结构体可以看到,其中有 const method_array_t methods()、const property_array_t properties() 等这样的方法,因此可以推测类结构中的方法列表、属性列表是从这里获取的。以const method_array_t methods()为例,获取类的实例方法也是通过调用methods()来获取的。比如说runtime函数class_copyMethodList,其代码如下:

Method *
class_copyMethodList(Class cls, unsigned int *outCount)
{
    unsigned int count = 0;
    Method *result = nil;

    if (!cls) {
        if (outCount) *outCount = 0;
        return nil;
    }

    mutex_locker_t lock(runtimeLock);
    const auto methods = cls->data()->methods();
    
    ASSERT(cls->isRealized());

    count = methods.count();

    if (count > 0) {
        result = (Method *)malloc((count + 1) * sizeof(Method));
        
        count = 0;
        for (auto& meth : methods) {
            result[count++] = &meth;
        }
        result[count] = nil;
    }

    if (outCount) *outCount = count;
    return result;
}

其中const auto methods = cls->data()->methods()就是通过class_rw_t的methods()方法获取实例方法列表。但是在class_rw_t结构体中,我们并没有看到结构体里由存储方法列表相关的属性。反而是在methods()方法里看到读取方法列表是从class_rw_ext_t或者class_ro_t结构体。其实不只是methods,包括properties和protocols也是一样的。那么他们之间到底有什么关系呢?
接下来我们先来看看class_rw_ext_t 和 class_ro_t的数据结构,看看为什么class_rw_t可以从他们这里读取类相关信息。

class_rw_ext_t

class_rw_ext_t是个结构体,其数据结构如下:

struct class_rw_ext_t {
    const class_ro_t *ro;
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    char *demangledName;
    uint32_t version;
};

由此可知,结构体class_rw_ext_t中的确有方法列表、属性列表和协议等信息,而且还有一个class_ro_t指针。接下来继续看class_ro_t。

class_ro_t

以下是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;
    // This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
    _objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];
    _objc_swiftMetadataInitializer swiftMetadataInitializer() const {
        if (flags & RO_HAS_SWIFT_INITIALIZER) {
            return _swiftMetadataInitializer_NEVER_USE[0];
        } else {
            return nil;
        }
    }
    method_list_t *baseMethods() const {
        return baseMethodList;
    }
    class_ro_t *duplicate() const {
        if (flags & RO_HAS_SWIFT_INITIALIZER) {
            size_t size = sizeof(*this) + sizeof(_swiftMetadataInitializer_NEVER_USE[0]);
            class_ro_t *ro = (class_ro_t *)memdup(this, size);
            ro->_swiftMetadataInitializer_NEVER_USE[0] = this->_swiftMetadataInitializer_NEVER_USE[0];
            return ro;
        } else {
            size_t size = sizeof(*this);
            class_ro_t *ro = (class_ro_t *)memdup(this, size);
            return ro;
        }
    }
};

这里面我们发现class_ro_t不仅baseMethodList、 baseProtocols、baseProperties等信息,还有成员变量const ivar_list_t * ivars等其他信息。在了解他们的结构之后,我们从结构上简单做一下它们之间关系的分析:首先class_rw_t 有一个指针ro_or_rw_ext,ro_or_rw_ext指向可能是class_rw_ext_t或者是class_ro_t,而ro_or_rw_ext又有一个指针指向class_ro_t。ro_or_rw_ext指针的作用是,当读取类相关信息时,会优先判断是否指向class_rw_ext_t,如果是class_rw_ext_t就从它读取,没有就是class_ro_t,直接从class_ro_t读取。那么在程序运行中到底ro_or_rw_ext什么时候是class_rw_ext_t,什么时候是class_ro_t呢?其实WWDC2020有过对这三个数据结构的介绍,下面会做简单的总结。

class_ro_t&class_rw_t&class_rw_ext_t&Class之间的关系

要了解它们之间的关系,我们首先要了解几个关键跟它们相关的概念。

  • 名词解析
    从字面意思解读:class_ro_t中的“ro”代表只读;class_rw_t中的“rw”代表可读可写;class_rw_ext_t中的“rw_ext”代表可读可写的扩展。
    干净内存和脏内存:干净内存内存指的是内存一旦加载就不会被改变。反之,脏内存就是在运行时会被修改的。
  • 关系释疑
    Class本身是运行时加载的,在运行时会被改变,所以本身Class就是属于脏内存。那么如果想要获取Class的干净内存,也就是编译时确定的数据结构包括方法列表、成员变量等的,该怎么办?这其实就是class_ro_t的作用。因为class_ro_t是只读,意味着class_ro_t是从mach-o读取类的数据之后,就不会被改变。那如果我们想在运行时修改类的信息,比如添加方法,比如加载category怎么办呢?那这时候就有一个与之对应的class_rw_t结构,class_rw_t是运行时存储类的信息,可读可写的,可以在运行时修改。说到这里,好像还漏掉一个结构class_rw_ext_t,这个东西又是干什么用的呢?存在的意义是什么?其实还是跟运行时有关。实际上在我们的app运行中,需要运行时修改的类是非少的,据统计平均大概就10%左右。那也就是说大部分只需要读取class_ro_t中的数据就够了,少部分才需要修改。因此才会有class_rw_ext_t这个扩展的结构体。class_rw_ext_t的作用是这样的:当我们需要修改类结构时,比如添加方法、加载category等时,class_rw_t回去开辟一个额外的空间rwe(class_rw_ext_t),用于存储新的方法和class_ro_t中的方法等信息。这样做的目的有一个好处就是,对于绝大部分类是不需要这个开辟class_rw_ext_t这个结构体,节省内存。
  • 创建时机
    那这几个结构分别是什么时候创建的呢?这里就设计到类的加载流程。首先类在app启动的时候会被映射到内存,这时候会先创建Class(objc_class)结构,然后把类编译时的类数据映射到class_ro_t,class_ro_t结构体指针存储到Class的bits指针中,我们前面提到类的bits中存储的是class_rw_t指针,实际上在类初始化之前这里存储的是class_ro_t,等到类初始化的时候会创建一个class_rw_t结构,然后通过data()从bits中读取class_ro_t,然后class_rw_t通过set_ro(const class_ro_t *ro)把指针ro_or_rw_ext指向这个class_ro_t,然后Class通过setData()把class_rw_t指针存储到bits里面。然后在运行时根据需要,根据extAllocIfNeeded或extAlloc创建class_rw_ext_t,然后把class_ro_t关联到class_rw_ext_t,这就是大概流程。详细类的加载流程点击这里了解;

备注:category为什么不能添加成员变量?因为category是运行时添加的,他只能操作class_rw_t结构,但是class_rw_t结构没有添加成员变量的入口。成员变量是存储在class_ro_t中的,是无法被修改的。

类中的方法列表、属性列表和协议的结构

这里只对方法列表结构分析,其他两个类似,可以类推。

  • method_array_t & method_list_t
    首先我们读取方法列表methods时获取的是method_array_t结构体,但是class_ro_t的方法列表baseMethodList是method_list_t结构体,他们之间存在什么样的而关系呢?一下我们通过源码大概分析一下,一下是method_array_t的结构体:
class method_array_t : 
    public list_array_tt 
{
    typedef list_array_tt Super;

 public:
    method_array_t() : Super() { }
    method_array_t(method_list_t *l) : Super(l) { }

    method_list_t * const *beginCategoryMethodLists() const {
        return beginLists();
    }
    method_list_t * const *endCategoryMethodLists(Class cls) const;

    method_array_t duplicate() {
        return Super::duplicate();
    }
};

这里我们发现method_array_t是一个数组,而它的元素就是method_list_t。method_list_t的结构体如下:

struct method_list_t : entsize_list_tt {
    bool isUniqued() const;
    bool isFixedUp() const;
    void setFixedUp();
    uint32_t indexOfMethod(const method_t *meth) const {
        uint32_t i = 
            (uint32_t)(((uintptr_t)meth - (uintptr_t)this) / entsize());
        ASSERT(i < count);
        return i;
    }
};

可见method_list_t也是个数组,它的元素就是method_t,method_t是方法的结构体,其中包含有方法名称(SEL)、方法签名和方法实现三个指针信息。baseMethodList在启动时会被重新排序,根据SEL的地址大小进行排序。这也是我们方法列表查找的时候需要二分法查找的原因;而class_rw_ext_t如果存在的话,methods是method_array_t类型的,是个二维数据,它的排序是最新加入的数组会放到最前面。比如说category方法运行时加载,总是在baseMethodList的前面。这也是为什么category方法与本类方法同名时,category方法会先被调用的原因。

  • property_array_t & property_list_t
    与方法列表类似,这里不在分析,以下展示的是属性列表相关的结构:
//class_rw_ext_t中的property_array_t properties对应的数据结构如下:
class property_array_t : 
    public list_array_tt 
{
    typedef list_array_tt Super;
 public:
    property_array_t() : Super() { }
    property_array_t(property_list_t *l) : Super(l) { }

    property_array_t duplicate() {
        return Super::duplicate();
    }
};

//而class_ro_t中的property_list_t baseProperties对应的数据结构如下:
struct property_list_t : entsize_list_tt {
};
  • protocol_array_t & protocol_list_t
    与方法列表类似,这里不在分析,以下展示的是协议列表相关的结构:

//class_rw_ext_t中的protocol_array_t protocols对应的数据结构如下:
class protocol_array_t : 
    public list_array_tt 
{
    typedef list_array_tt Super;

 public:
    protocol_array_t() : Super() { }
    protocol_array_t(protocol_list_t *l) : Super(l) { }

    protocol_array_t duplicate() {
        return Super::duplicate();
    }
};

//而class_ro_t中的protocol_list_t baseProtocols对应的数据结构如下:
struct protocol_list_t {
    // count is pointer-sized by accident.
    uintptr_t count;
    protocol_ref_t list[0]; // variable-size

    size_t byteSize() const {
        return sizeof(*this) + count*sizeof(list[0]);
    }

    protocol_list_t *duplicate() const {
        return (protocol_list_t *)memdup(this, this->byteSize());
    }

    typedef protocol_ref_t* iterator;
    typedef const protocol_ref_t* const_iterator;

    const_iterator begin() const {
        return list;
    }
    iterator begin() {
        return list;
    }
    const_iterator end() const {
        return list + count;
    }
    iterator end() {
        return list + count;
    }
};

总结

根据源码的分析,我们可以大致的绘制类的结构图:

类底层结构.jpg

首先类的底层结构objc_class继承自objc_object,它本身也是一个对象。在程序运行过程中,它以单利的形式存在。作为对象,它也有一个isa指针指向自己的类,也就是元类,元类存储了类对象的实例方法,也就是我们的类方法;superclass指向父类,也可通过class_rw_t的firstSubclass指针指向之类,类的继承关系是一个双向链表;cache负责缓存,缓存方法列表,一遍方法快速查找;bits运行时保存了class_rw_t指针。通过class_rw_t可以访问class_ro_t,class_ro_t是类编译时的结构,比如成员变量、方法列表等等,是只读的干净内存,运行时不能修改。class_rw_t为类的的运行时的结构,可读可写,所以运行时添加的方法、属性等都是通过操作class_rw_t的class_rw_ext_t来实现。class_rw_ext_t是为class_rw_t在需要是额外开辟的内存,是运行时懒加载的。只有在需要修改信相关信息比如方法、category等时才会被创建。避免每个class_rw_t创建一个class_rw_ext_t,浪费内存。(了解类的加载过程可以点击);

你可能感兴趣的:(OC类的底层结构objc_class)