在iOS-isa指向图&类结构中提到了类的结构,并分析了isa
和bits
中的内容,superclass
结构与isa
相同,就不多做分析了,今天我们就来看看cache
里都有些什么有意思的东西吧。
类结构图
其实按照咱们之前的知识栈,其实很容易猜到,这里就是存储方法列表的嘛,嗯,很简单嘛~ 真的简单吗?我们先看看他的数据结构吧。
cache_t数据结构
可以看到其中用了联合体
union
,union
相关可以看看iOS-对象的本质,ISA分析,由此我们可以知道,
cache_t
会占用16字节。其实细心的朋友会发现上面的图并不是cache_t
的全部内容,其后还有类似下图的内容image.png
后面还有这么多变量你怎么就算出cache_t只占用16字节呢,其实这是变量都是
static
修饰,会存到全局区,不会增大cache_t
的大小。
接下来看看cache_t
里的方法,会发现insert
方法,按照cache_t缓存方法列表的作用,大致能猜到这个方法就是将新数据插入列表的方法。
cache_t部分方法
看看insert的实现吧
insert实现
会发现里面用到bucket_t
这个结构体,并且对他进行一些操作,猜测这是一个很重要的结构,来康康吧。
image.png
嘿嘿嘿,终于看到了熟悉的东西——
sel
和imp
。显然咱们的缓存方法列表得到印证。且是借助bucket_t
这一结构体缓存的。
接下来我们通过lldb
来验证一下:
1.断点执行到下图:
image.png
2.通过
lldb
调试:
(lldb) p/x pclass //拿到类对象
(Class) $0 = 0x0000000100008930 LGPerson
(lldb) p (cache_t *)0x0000000100008940//平移16字节,因为cache_t前面有isa和superclass
(cache_t *) $1 = 0x0000000100008940
(lldb) p *$1//输出cache_t
(cache_t) $2 = {
_bucketsAndMaybeMask = {
std::__1::atomic<unsigned long> = {
Value = 4301517904
}
}
= {
= {
_maybeMask = {
std::__1::atomic<unsigned int> = {
Value = 3
}
}
_flags = 32816
_occupied = 1
}
_originalPreoptCache = {
std::__1::atomic<preopt_cache_t *> = {
Value = 0x0001803000000003
}
}
}
}
(lldb) p $2.buckets()//分析源码,找到buckets()方法获取buckets
(bucket_t *) $3 = 0x000000010063f450
(lldb) p *$3//获取第一个bucket,发现是空的。
(bucket_t) $4 = {
_sel = {
std::__1::atomic<objc_selector *> = (null) {
Value = (null)
}
}
_imp = {
std::__1::atomic<unsigned long> = {
Value = 0
}
}
}
(lldb) p $2.buckets()[1]//获取第二个bucket,找到了已执行过的say1,注意buckets是结构体,不是数组,那为什么可以用下标取值呢,其实这里是取内存平移,[1]相当于平移1个单位
(bucket_t) $5 = {
_sel = {
std::__1::atomic<objc_selector *> = "" {
Value = ""
}
}
_imp = {
std::__1::atomic<unsigned long> = {
Value = 45088
}
}
}
(lldb) p $5.sel()
(SEL) $6 = "say1"
到这虽然我们找到了say1
的缓存,但为什么他没有存在第一个位置呢???嘤嘤嘤?
又没办法偷懒了,只有看看insert
方法是咋把他存进去的了。
void cache_t::insert(SEL sel, IMP imp, id receiver)
{
runtimeLock.assertLocked();
// Never cache before +initialize is done
if (slowpath(!cls()->isInitialized())) {
return;
}
if (isConstantOptimizedCache()) {
_objc_fatal("cache_t::insert() called with a preoptimized cache for %s",
cls()->nameForLogging());
}
#if DEBUG_TASK_THREADS
return _collecting_in_critical();
#else
#if CONFIG_USE_CACHE_LOCK
mutex_locker_t lock(cacheUpdateLock);
#endif
ASSERT(sel != 0 && cls()->isInitialized());
// Use the cache as-is if until we exceed our expected fill ratio.
mask_t newOccupied = occupied() + 1;//occupied()直接return _occupied
unsigned oldCapacity = capacity(), capacity = oldCapacity;//capacity()直接return mask() ? mask()+1 : 0;
if (slowpath(isConstantEmptyCache())) {//判断缓存是否为空,相当于第一次进来
// Cache is read-only. Replace it.
if (!capacity) capacity = INIT_CACHE_SIZE;//INIT_CACHE_SIZE 为 4,由此可见初始化分配4个存储空间
reallocate(oldCapacity, capacity, /* freeOld */false);//分配空间,初始化_occupied为0,设置_bucketsAndMaybeMask中MaybeMask为capacity-1
}
else if (fastpath(newOccupied + CACHE_END_MARKER <= cache_fill_ratio(capacity))) {//判断空间是否占用超过3/4,没超过则不处理
// Cache is less than 3/4 or 7/8 full. Use it as-is.
}
#if CACHE_ALLOW_FULL_UTILIZATION
else if (capacity <= FULL_UTILIZATION_CACHE_SIZE && newOccupied + CACHE_END_MARKER <= capacity) {
// Allow 100% cache utilization for small buckets. Use it as-is.
}
#endif
else {//超过3/4则进行扩容,直接2倍扩容
capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
if (capacity > MAX_CACHE_SIZE) {
capacity = MAX_CACHE_SIZE;
}
reallocate(oldCapacity, capacity, true);//最后一个参数为true,表示需要释放旧的内存,说明每次扩容都会重新分配内存,并释放旧内存,
}
//以上是计算要分配的空间,下面就是插入的逻辑啦
bucket_t *b = buckets();//拿到bucket_t
mask_t m = capacity - 1;//这里也是maybemask,是存在_bucketsAndMaybeMask中的
mask_t begin = cache_hash(sel, m);//通过hash计算存储位置,
mask_t i = begin;
// Scan for the first unused slot and insert there.
// There is guaranteed to be an empty slot.
do {
if (fastpath(b[i].sel() == 0)) {//判断当前位置是否有值
incrementOccupied();
b[i].set<Atomic, Encoded>(b, sel, imp, cls());//插入数据到i,也就是begin
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));
bad_cache(receiver, (SEL)sel);
#endif // !DEBUG_TASK_THREADS
}
有了源码,就是yyds,可以看到
mask_t begin = cache_hash(sel, m);//通过hash计算存储位置,
原来是通过hash来计算下标,难怪不是从第一个位置存储的。
关键属性解释
1、_mask是什么?
_mask是指掩码数据,用于在哈希算法或者哈希冲突算法中计算哈希下标,其中mask 等于capacity - 1
2、_occupied 是什么?
_occupied表示哈希表中 sel-imp 的占用大小 (即可以理解为分配的内存中已经存储了sel-imp的的个数),
init会导致occupied变化
属性赋值,也会隐式调用,导致occupied变化
方法调用,导致occupied变化