cache_t的探究

前言

今天我们要探究的cache_t在之前的类的结构分析中看到过,在objc_class中存在一个cache_t类型的成员cachecache顾名思义缓存,那存的是什么呢?

cache存的是什么

首先我们看一下cache_t的源码实现:

struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    //macOS、模拟器
    explicit_atomic _buckets;
    explicit_atomic _mask;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    //真机 64位
    explicit_atomic _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
    //真机 非64位
    // _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 _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;
//部分代码省略
...
}

这里进行了一个架构的判断:
1、CACHE_MASK_STORAGE_OUTLINED表示macOS模拟器
2、CACHE_MASK_STORAGE_HIGH_16表示64位的真机
3、CACHE_MASK_STORAGE_LOW_4表示非64位的真机

我们可以看到它们都有一个explicit_atmic(显示原子性),使用这个主要是确保增删改查时的安全性,而模拟器中的_buckets_mask真机中它们合并成了_maskAndBuckets,这样做的目的是为了节省内存,读取方便

bucket的意思,哪里面装的是什么呢?这里我们进入bucket_t看一下:

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 _imp;
    explicit_atomic _sel;
#else
    explicit_atomic _sel;
    explicit_atomic _imp;
#endif
//部分代码省略
...
}

我们可以看到里面装的是selimp,同时这里也进行了架构的判断:分为真机非真机,区别在于selimp的顺序不同。

总结
cache中存储的是sel-imp

cache中查找sel-imp

既然cache_t中存的是sel-imp,哪又是如何查找的呢?

通过源码查找

首先我们定义一个LGPerson类,声明了两个属性以及5个方法

@interface LGPerson : NSObject

@property (nonatomic, copy) NSString *lgName;
@property (nonatomic, strong) NSString *nickName;

- (void)sayHello;

- (void)sayCode;

- (void)sayMaster;

- (void)sayNB;

+ (void)sayHappy;

@end

main函数中执行如下代码,并在执行第一个方法处打个断点:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        LGPerson *p  = [LGPerson alloc];
        Class pClass = [LGPerson class];
//        p.lgName     = @"Cooci";
//        p.nickName   = @"KC";
        // 缓存一次方法 sayHello
        // 4
        [p sayHello];
        [p sayCode];
        [p sayMaster];
//        [p sayNB];


        NSLog(@"%@",pClass);
    }
    return 0;
}

这时我们通过lldb进行调试:

lldb-cache_t

可以看出方法执行前后,_buckets_mask_occupied都发生了变化,我们发现有一个struct bucket_t *buckets()可以打印buckets
buckets

但是_sel_imp并不是我们要的,我们通过sel()imp(Class)打印看看:
sel

imp

发现这时打印出来的就是我们刚刚执行的方法sayHello,说明在没有调用方法前,cache是没有缓存的,在调用一次后cache中就有了一个缓存,即调用一次方法就会缓存一次方法

你可能感兴趣的:(cache_t的探究)