OC底层探索(七) cache_t分析

cache_t 源码

OC底层探索(五) 类的结构分析文章中,我们分析objc_class源码时,有提到过其属性 cache_t cache属性的大小16字节。

那么我们今天就着重分析以下cache_t到底是干什么的。

准备工作

  • 在LGPerson类中申请两个方法:
@interface LGPerson : NSObject
-(void)sayHello;
-(void)sayCode;
  • 在main.h中调用这两个方法,并打上断点
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *p = [LGPerson alloc];
        Class pClass = [LGPerson  class];
        [p sayHello];
        [p sayCode];
        NSLog(@"%@",p);//断点
    }
    return 0;
}

打印

  • 打印LGPerson首地址
  • objc_class中,cache属性在第位,前两位分别时8位的isa8位的superclass,所以首地址获取到cache_t

打印结果如下:
在这里插入图片描述

  • cache_t源码中,调用struct bucket_t *buckets();方法获取bucket_t类型数据。
  • bucket_t数据中分别由sel() imp(Class cls)方法获取方法。
  • 使用$13.sel() 获取到了sayHello方法,那么sayCode方法在哪呢?

打印结果如下:
在这里插入图片描述

  • 由于_buckets属性是一个数组,那么我们可以使用指针平移来获取下一个指针的值。
  • 打印*($12 + 1)就获取到sayCode方法。

在这里插入图片描述

源码分析

cache_t 源码
struct cache_t {
#if 1 // Mac 
    struct bucket_t * _buckets;
    mask_t _mask;
#elif 1 // 真机
    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;
#endif
    uint16_t _flags; // 标志位
    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();
    void reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld);
    void insert(Class cls, SEL sel, IMP imp, id receiver);
};

}

分析

  • 真机状态下,缓存信息存储在_maskAndBuckets
uintptr_t _maskAndBuckets;
    mask_t _mask_unused;
  • MAC状态下,缓存信息存储在_buckets中。
    struct bucket_t * _buckets;
    mask_t _mask;
  • 根据buckets()方法读取缓存信息
struct bucket_t *buckets();
  • 根据insert()方法向缓存中添加数据
void insert(Class cls, SEL sel, IMP imp, id receiver);
  • 根据occupied()方法获取方法的总个数
   mask_t occupied();
cache_t::insert 源码
ALWAYS_INLINE
void cache_t::insert(Class cls, SEL sel, IMP imp, id receiver)
{


    // 1、 计算相关的occupied
    mask_t newOccupied = occupied() + 1;
    unsigned oldCapacity = capacity(), capacity = oldCapacity;
   // 2、 判断如果是创建 进行初始化
    if (slowpath(isConstantEmptyCache())) {
        // Cache is read-only. Replace it.
        if (!capacity) capacity = INIT_CACHE_SIZE;
        reallocate(oldCapacity, capacity, /* freeOld */false);
    }
//3 判断是否需要扩容
    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.
    }
//4 扩容操作;
    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;

    //5;进行相关的方法存储
    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. 计算相关的occupied

  • occupied()默认值为0
  • 每次调用插入缓存方法时都会加
  • 所以在cache_t源码中occupied是缓存中方法的总个数。

2. 判断如果是创建 进行初始化

  • 判断当前cache是否为空,如果为空,就申请4字节内存,并初始化。

  • 其中INIT_CACHE_SIZE 为申请内存空间的大小,
    0001 => 左移2位 => 0100

INIT_CACHE_SIZE_LOG2 = 2,
INIT_CACHE_SIZE = (1 << INIT_CACHE_SIZE_LOG2),
  • reallocate 初始化Cache
void cache_t::setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask)
{

#ifdef __arm__
    mega_barrier();
    _buckets.store(newBuckets, memory_order::memory_order_relaxed);
    mega_barrier();
    _mask.store(newMask, memory_order::memory_order_relaxed);
    _occupied = 0;
#elif __x86_64__ || i386
    _buckets.store(newBuckets, memory_order::memory_order_release);
    _mask.store(newMask, memory_order::memory_order_release);
    _occupied = 0;
#else

3. 判断是否需要扩容:如果新的值小于或等于原来的3/4,不做任何处理;大于3/4就扩容2倍

 fastpath(newOccupied + CACHE_END_MARKER <= capacity / 4 * 3)
  • 其中newOccupied是当前需要插入方法的个数
  • CACHE_END_MARKER 是固定值
  • capacity申请的总内存空间的大小
 capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;  // 扩容两倍 4
        if (capacity > MAX_CACHE_SIZE) {
            capacity = MAX_CACHE_SIZE;
        }
        reallocate(oldCapacity, capacity, true); 
  • 当新的值大于3/4时,以当前内存大小为基础扩容两倍
  • 重新初始化,之前存储的方法全部都清空,因为扩容是将重新开辟一块内存空间。

4. hash方式存储方法

 mask_t begin = cache_hash(sel, m);
   mask_t i = begin;


static inline mask_t cache_hash(SEL sel, mask_t mask) 
{
    return (mask_t)(uintptr_t)sel & mask;
}
  • cache存储中OC使用的是hash方式存储
  • 其中maskmask = capacity - 1; 总的内存大小减一
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值