阅读 71

iOS底层探索--cache_t分析

小谷底层探索合集

  • 我之前的博客介绍了objc_class中的bits类的结构探索,当时我们为了探究结构,略掉了cache_t,今天我们就来分析一波这是个什么东东~

  • 首先给大家介绍2个东西(我知道大家都知道。不过,还是要说一下,万一有不知道的呢。例如:我😆)

    • SEL:简单来说就是方法编号
    • IMP : 一个指向方法实现的指针(这两个我就不解释了,如果真不知道的话,百度一哈就行了)
  • 开始步入正题!!

1. 定位cache_t的结构

我们要先看看cache_t 是什么样子的,然后我们在一步步分析。

    1. 首先找到objc_class
  • 2.我们点进去就可以观察到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;

public:
    static bucket_t *emptyBuckets();
    
    struct bucket_t *buckets();
    mask_t mask();
    mask_t occupied();
    void incrementOccupied();
    void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
    void initializeToEmpty();

    unsigned capacity();
    bool isConstantEmptyCache();
    bool canBeFreed();
//不继续复制了,,太多了。有条件的自己可以研究下
复制代码
    1. 我们就可以看到cache_t的结构了,不过,这么长的代码,这不是搞咱们心态吗。看看传说中的if-else~

宏定义:

#define CACHE_MASK_STORAGE_OUTLINED 1
#define CACHE_MASK_STORAGE_HIGH_16 2
#define CACHE_MASK_STORAGE_LOW_4 3

#if defined(__arm64__) && __LP64__
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16
#elif defined(__arm64__) && !__LP64__
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_LOW_4
#else
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_OUTLINED
#endif
复制代码
  • CACHE_MASK_STORAGE_HIGH_16 : 真机&&64位

  • CACHE_MASK_STORAGE_LOW_4 : 真机&&非64位

  • CACHE_MASK_STORAGE_OUTLINED:模拟器和Mac呗

我们今天来根据Mac讲解(毕竟会一个,其他的都差不多。。)

2. cache_t结构分析

2.1 通过查看源码分析

  • 我们第一眼望去,有效的东西:bucketsmaskflagsoccupied,flags看着就是标记,先略过。

  • 刚想开始分析,不过代码又长、英文有不行,看这个就烦!但没有好办法!只能咬咬牙(研究底层就要试着探索,可能探索的方向不对,会做无用功,但是如果不探索,都不知道对不对!)

  • 我们先看下buckets的结构:

#if __arm64__
    explicit_atomic<uintptr_t> _imp;
    explicit_atomic<SEL> _sel;
#else
    explicit_atomic<SEL> _sel;
    explicit_atomic<uintptr_t> _imp;
#endif
复制代码

这特么不就是存储SELIMP么。我擦,那我迫不及待要验证一波 了,兄弟们~

2.2. 通过lldb调试分析

2.2.1 调用单个方法分析

    1. 创建一个XGTest的类:
@interface XGTest : NSObject
- (void)say1;
- (void)say2;
- (void)say3;
- (void)say4;
- (void)say5;
@end

XGTest *test = [XGTest alloc];
Class tClass = [test class];
        
[test say1];
复制代码

通过调用方法say1,然后定位到cache的结构。

    1. 我们可以看到我们想要的东西了(_buckets_mask_flags_occupied),我们观察源码是否有可以获取的方法(还真有!):
    static bucket_t *emptyBuckets();
    
    struct bucket_t *buckets();
    mask_t mask();
    mask_t occupied();
    void incrementOccupied();
    void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
    void initializeToEmpty();
复制代码
    1. 所以我们可以通过调用buckets()查看结构

发现:这个buckets里面不就是存储sel和imp吗!!

    1. 我们通过观察,可以发现bucket_t中有获取selimp的方法

    1. 那还等什么!我要得到他!!

    1. 完美获取到了!

2.2.2 调用多个方法分析

我们调用一个方法cache的变化已经知道了,那么多个方法呢?

    1. 增加一个方法!
XGTest *test = [XGTest alloc];
Class tClass = [test class];
        
[test say1];
[test say2];
复制代码
    1. 我们打印下cache的结构:

    1. 我们发现 _occupied有变化,这个我们一会在分析,我们现在先了解 buckets

兄弟们! 看我操作!!

诶?我们的say2呢?咋没了。那我不要找一找?

    1. 那我大胆猜测一波!bucketss,那他是不是有多个?,会不会是个类似数组结构?那我通过指针偏移可不可以得到想要的东西?那验证一下呗!

    1. OK了,我们已经了解buckets里面存selimp了。

3. cache_t实现原理分析

刚才我们发现_occupied有变化,这个东西翻译是占位。。也有点意思~

  • 那我们看下,_occupied如何变化的?而且当方法增多,mask也改变。还有buckets怎么存的?

    1. 我们看occupied的变化。。咋变的?,那只能看看有没有让他变化的方法啊!
    static bucket_t *emptyBuckets();
    
    struct bucket_t *buckets();
    mask_t mask();
    mask_t occupied();
    void incrementOccupied();
    void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
    void initializeToEmpty();
复制代码

不辞辛苦!让我看到了incrementOccupied这!然后我们顺藤摸瓜~看看谁调用了它!

    1. 通过全局搜索!发现只有一个地方
void cache_t::insert(Class cls, SEL sel, IMP imp, id receiver)
复制代码
    1. 诶呀!终于摸到了敌人后方!,insert不就是插入吗!我有预感,这个就是我们要看的东西!(但是我没有证据😆)
    1. 那我要提供证据了啊!
void cache_t::insert(Class cls, SEL sel, IMP imp, id receiver)
{
#if CONFIG_USE_CACHE_LOCK
    cacheUpdateLock.assertLocked();
#else
    runtimeLock.assertLocked();
#endif

    ASSERT(sel != 0 && cls->isInitialized());

//前面是断言,,没有!!分为2部分看

//第一部分

    // Use the cache as-is if it is less than 3/4 full
    mask_t newOccupied = occupied() + 1;
    unsigned oldCapacity = capacity(), capacity = oldCapacity;
    if (slowpath(isConstantEmptyCache())) {
        // Cache is read-only. Replace it.
        if (!capacity) capacity = INIT_CACHE_SIZE;
        reallocate(oldCapacity, capacity, /* freeOld */false);
    }
    else if (fastpath(newOccupied + CACHE_END_MARKER <= capacity / 4 * 3)) { // 4  3 + 1 bucket cache_t
        // Cache is less than 3/4 full. Use it as-is.
    }
    else {
        capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;  // 扩容两倍 4
        if (capacity > MAX_CACHE_SIZE) {
            capacity = MAX_CACHE_SIZE;
        }
        reallocate(oldCapacity, capacity, true);  // 内存 库容完毕
    }

// 第二部分
    bucket_t *b = buckets();
    mask_t m = capacity - 1;
    mask_t begin = cache_hash(sel, m);
    mask_t i = begin;

    // Scan for the first unused slot and insert there.
    // There is guaranteed to be an empty slot because the
    // minimum size is 4 and we resized at 3/4 full.
    do {
        if (fastpath(b[i].sel() == 0)) {
            incrementOccupied();
            b[i].set<Atomic, Encoded>(sel, imp, cls);
            return;
        }
        if (b[i].sel() == sel) {
            // The entry was added to the cache by some other thread
            // before we grabbed the cacheUpdateLock.
            return;
        }
    } while (fastpath((i = cache_next(i, m)) != begin));

    cache_t::bad_cache(receiver, (SEL)sel, cls);
}
复制代码

刚开始给我整几个断言!增加代码长度?以为能唬住我看下去的欲望?岂不是在逗我笑!!

    1. 为了帮助分析,我把代码整理成两部分:首先if-else判断分析:

通过上图我们可以清楚内存开辟!

    1. 那么看他第二部分代码:

这就是cache实现原理,当然,也有好多方法没有具体说明,例如:cache_hash(如何哈希的) 这些就交给大家了,毕竟所有都写了,就没有探索的意义了!