iOS底层之类的结构到底有什么东西(4)

269 阅读5分钟

前言

上一章我们主要探索了类的对象本质和对象与类的关系,这一章我们主要来探索了类的结构,了解一下类里面存储的到底是什么东西,有什么作用。

类的结构

Class

我们要了解类,那我们就要研究一下Class,我们去源码那里看看Class里面存储了什么东西。

typedef struct objc_class *Class;

我们可以看到Class是一个对象,这么说类也是一个对象,我们一般叫类对象

objc_class

那我们再点击进去看下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() { 
        return bits.data();
    }

    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }
    
    /**省略几百行内容*/
}

这里的代码我们是有点熟悉的了,前面的几章也有提及的到。

那这里继承着 objc_object,我们先看下父类存放着什么东西。

struct objc_object {
private:
    isa_t isa;    
}

原来父类存放着就是isa指针。

上2章我们已经分析过isasuperclass。那这里我们主要看下cache_tclass_data_bits_t

cache_t

从名字上我们大概猜测这是一个用来缓存使用的,那到底是缓存什么东西呢?占用多大呢?

struct cache_t {

#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED

    explicit_atomic<struct bucket_t *> _buckets;

    explicit_atomic<mask_t> _mask;

#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16

    explicit_atomic<uintptr_t> _maskAndBuckets;

    mask_t _mask_unused;

    // How much the mask is shifted by.

    static constexpr uintptr_t maskShift = 48;

    // Additional bits after the mask which must be zero. msgSend

    // takes advantage of these additional bits to construct the value

    // `mask << 4` from `_maskAndBuckets` in a single instruction.

    static constexpr uintptr_t maskZeroBits = 4;

    // The largest mask value we can store.

    static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;

    // The mask applied to `_maskAndBuckets` to retrieve the buckets pointer.

    static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << (maskShift - maskZeroBits)) - 1;

    // Ensure we have enough bits for the buckets pointer.

    static_assert(bucketsMask >= MACH_VM_MAX_ADDRESS, "Bucket field doesn't have enough bits for arbitrary pointers.");

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

    mask_t _mask_unused;

    static constexpr uintptr_t maskBits = 4;

    static constexpr uintptr_t maskMask = (1 << maskBits) - 1;

    static constexpr uintptr_t bucketsMask = ~maskMask;

#else

#error Unknown cache mask storage type.

#endif

#if __LP64__

    uint16_t _flags;

#endif

    uint16_t _occupied;
}

_buckets:为结构体指针,是一个散列表。

_mask:为当前最大可存储容量。

_occupied:为缓存方法的数量。

buckets

我们看一下buckets里面到底存放着什么东西。

struct bucket_t {

private:

    // IMP-first is better for arm64e ptrauth and no worse for arm64.

    // SEL-first is better for armv7* and i386 and x86_64.

#if __arm64__

   explicit_atomic<uintptr_t> _imp;

    explicit_atomic<SEL> _sel;

#else

    explicit_atomic<SEL> _sel;

    explicit_atomic<uintptr_t> _imp;

#endif

bucket_t里面有_imp_sel属性。明显,这里就是用来存储缓存的方法

cache_fill

我们调用对象的方法后,就会走objc_msgSend流程,就会进来cache_fill这个方法。

void cache_fill(Class cls, SEL sel, IMP imp, id receiver)
{
    runtimeLock.assertLocked();
#if !DEBUG_TASK_THREADS
    
    // Never cache before +initialize is done
    if (cls->isInitialized()) {
        cache_t *cache = getCache(cls);

#if CONFIG_USE_CACHE_LOCK
        mutex_locker_t lock(cacheUpdateLock);
#endif
        cache->insert(cls, sel, imp, receiver);
    }

#else
    _collecting_in_critical();
#endif
}

这里的意思是如果类已经初始化,然后会获取cache,调用cache->insert方法。

我们直接在代码里面进行分析。

void cache_t::insert(Class cls, SEL sel, IMP imp, id receiver)
{
    ASSERT(sel != 0 && cls->isInitialized());

    // Use the cache as-is if it is less than 3/4 full

    // newOccupied为新的方法缓存数量 = 缓存方法数量 + 1。

    mask_t newOccupied = occupied() + 1;

    // 获取当前容量,为mask+1
    unsigned oldCapacity = capacity(), capacity = oldCapacity;

    // 如果缓存是空的
    if (slowpath(isConstantEmptyCache())) {
        // Cache is read-only. Replace it.
        //如果容量capacity为0 ,则初始化为 4
        if (!capacity) capacity = INIT_CACHE_SIZE;

        // 优化容量空间,这里传false
        reallocate(oldCapacity, capacity, /* freeOld */false);
    }

    //如果newOccupied < 容量的4分之三,代表存储空间充足,不用处理
    else if (fastpath(newOccupied + CACHE_END_MARKER <= capacity / 4 * 3)) {
        // Cache is less than 3/4 full. Use it as-is.
    }

    // 超过3/4
    else {
        // 对当前容量进行扩容,为原来的2倍。
        capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
        // 容量大于 16, 则为16,即最大为16
        if (capacity > MAX_CACHE_SIZE) {
            capacity = MAX_CACHE_SIZE;
        }

        // 优化容量空间,这里传true
        reallocate(oldCapacity, capacity, true);
    }

    // 下面是根据散列表是否有该方法的缓存,没有存在该方法则插入缓存。
    bucket_t *b = buckets();

    mask_t m = capacity - 1;

    mask_t begin = cache_hash(sel, m);

    mask_t i = begin;

    do {

        if (fastpath(b[i].sel() == 0)) {

            incrementOccupied();

            b[i].set<Atomic, Encoded>(sel, imp, cls);

            return;

        }

        if (b[i].sel() == sel) {
            return;
        }

    } while (fastpath((i = cache_next(i, m)) != begin));

    cache_t::bad_cache(receiver, (SEL)sel, cls);

}

这里面有2处调用到reallocate,1个传true,1个传false。我们先看看里面做了什么处理。

void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld)
{
    bucket_t *oldBuckets = buckets();

    // 开辟空间
    bucket_t *newBuckets = allocateBuckets(newCapacity);

    ASSERT(newCapacity > 0);

    ASSERT((uintptr_t)(mask_t)(newCapacity-1) == newCapacity-1);

    // 设置Buckets和mask,mask为新容量-1。

    setBucketsAndMask(newBuckets, newCapacity - 1);
    
    if (freeOld) {
        //为true,则清空缓存信息
        cache_collect_free(oldBuckets, oldCapacity);
    }
}

下面添加一下容量capacitymask的关系代码。

unsigned cache_t::capacity()
{
    return mask() ? mask()+1 : 0; 
}

chche方法总结

到这里,我们总结一下上面做了什么。

  1. 先初始化缓存空间。有1个调用方法进来,先给方法缓存数newOccupied+1操作,用来和容量capacity进行对比。

  2. 先判断缓存是否为空,一开始缓存是空的,我们初始化容量capacity为4。这时候,Occupied方法占用数不会超过我们初始化容量capacity的4分之3,散列表buckets则存储该方法。

  3. 后面陆续再调用几个方法后,缓存不为空,方法缓存数Occupied超过容量我们初始化容量capacity的4分之三,则对容量capacity进行扩容,为原来的2倍且最大为16。同时释放已有的缓存信息。重新存储新的方法。

  4. 存储方法的时候,先散列表进行遍历,没有找到缓存方法,则方法缓存数Occupied加1,如果散列表里面本来就有,就不作处理。

class_data_bits_t

struct class_data_bits_t {

    friend objc_class;

    uintptr_t bits;

public:
    class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }

    void setData(class_rw_t *newData)
    {
        ASSERT(!data()  ||  (newData->flags & (RW_REALIZING | RW_FUTURE)));

        uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;

        atomic_thread_fence(memory_order_release);

        bits = newBits;
    }
}

结合一开始的bits.data()bits.setdata()我们就可以就可以get和setclass_rw_t

class_rw_t

struct class_rw_t {

    uint32_t flags;

    uint16_t witness;

#if SUPPORT_INDEXED_ISA

    uint16_t index;

#endif

    explicit_atomic<uintptr_t> ro_or_rw_ext;

    Class firstSubclass;

    Class nextSiblingClass;

private:
    using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t *, class_rw_ext_t *>;
    
    const ro_or_rw_ext_t get_ro_or_rwe() const {
        return ro_or_rw_ext_t{ro_or_rw_ext};
    }
}

这里的ro_or_rw_ext,编译时值是class_ro_t,编译后类实现完成时的值是 class_rw_ext_t,而编译时的 class_ro_t 作为 class_rw_ext_tconst class_ro_t *ro 成员变量保存。

class_rw_ext_t

类中的属性、方法还有遵循的协议等信息都保存在 class_rw_ext_t中。

class_rw_ext_t在运行时会把class_ro_t的内容拷贝过来作为属性,然后再将当前类的分类的这些属性方法等拷贝到其中。

当然实际访问类的方法、属性等也都是访问的class_rw_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 中存储信息有以下:

  • ro : class_ro_t。
  • methods:方法列表,包括分类方法。
  • properties:属性列表。
  • protocols:协议列表。
  • demangledName:所属类名。

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

这里的class_ro_t 中存储信息有以下:

  • baseMethodList : 方法列表。
  • baseProperties:属性列表 。
  • baseProtocols:协议列表。
  • ivars:成员变量列表。

bits总结

从上面分析可以看到类对象的主要信息, 都存在了 class_rw_ext_tclass_ro_t 两个结构体中。

总结

通过上面的分析,我们就很清楚知道类的结构有isa指针,父类superClass,缓存方法cache,以及bits(包含方法列表属性列表协议列表成员变量列表)等信息。

那下一章,我们就详细聊聊方法的本质是什么,以及调用的流程是怎样的。