本章内容
- 类的cache结构,以及重要成员以及方法
- bucket_t的结构
- LLDB 进行验证cache缓存的方法
- 源码分析insert方法
类的第三个成员cache
类的本质以及结构已经清楚。类的本质就是objc_class *结构体指针。而成员从上到下依次是:
- Class isa,类的isa指向了其元类
- Class superClass,类的父类
- cache_t cache,类的缓存,缓存的是执行的方法
- class_data_bits_t bits,类的方法,协议,属性,成员变量等存储的地方。
cache的结构
cache顾名思义就是缓存,而且cache的存在也与objc_msgSend有着极大联系,我们可以想着OC的消息发送,转发的时候就能想到,它如果要想提高消息发送转发的效率就一定会有缓存这个东西,因为有缓存在快速发消息,转发的时候就会提高速率。所以我们研究类的cache也很有必要。我们为什么要引出下面的buckets其实从它结构体成员和方法可以看出来其重要程度,而buckets究竟是什么,请看下面的介绍
重要成员
-
_bucketsAndMaybeMask:代表了buckets和maybeMask。它的作用其实如对象的isa一样就是为了用更少的内容却可以存储更多的信息。
-
联合体:可以看到有个
__LP64__代表了在MAC OS X 等系统运行,所以我们只用看结构体就行了1. 结构体{ _maybeMask: 代表容器的大小。以每3/4的时候进行开辟,也就是 _occupied/_maybeMask为3/4进行开辟 _occupied: 代表了缓存方法的数量 } 2. _originalPreoptCache 复制代码

重要方法
从上之下进行说明,其中 insert 方法是最重要的,因为如果我们要进行读取一个缓存方法的时候,首先要这个方法在缓存里面,也就肯定要先去insert ,然后再去objc_msgSend。
-
mask_t mask() const,获取maybeMask值,也就是容器的大小。
-
void incrementOccupied(),提升occupied的大小,每次+1。
-
void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask),将新的buckets存储到_bucketsAndMaybeMask里面,将新的mask(容器)存储到_maybeMask里面。
-
void reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld),创建新的buckets,其容器大小为newCapacity,并调用方法3 去存储,根据freeOld看是否需要释放旧的buckets。它其实内部调用了方法7
-
void collect_free(bucket_t *oldBuckets, mask_t oldCapacity),暂时不知道
-
static bucket_t *emptyBuckets(),清空buckets,这么说不准确,应该是将指针清空。
-
static bucket_t *allocateBuckets(mask_t newCapacity),创建buckets
-
static bucket_t *emptyBucketsForCapacity(mask_t capacity, bool allocate = true),如果满足条件调用方法6,将buckets清空,如果allocate为false的时候会直接返回nil,不满足的话要循环清除。说的并不特别准确,可以看源码
—–以上1-8都是私有方法,9开始是共有—-
-
struct bucket_t *buckets() const, 获取buckets,LLDB调试会用
-
mask_t occupied() const,获取 _occupied,缓存的方法数量
-
void insert(SEL sel, IMP imp, id receiver),插入方法缓存,很重要。
bucket_t结构
我们根据cache就已经知道了buckets的重要性,但是buckets是什么呢?
bucket_t * 是一个结构体指针,它只是指向了一个内存中的地址,但是它代表什么其实我们是不确定的。例如void *它也仅仅是一个指针,指向内存地址。但是我们取出来的是一串16进制的数据而已。为什么我们平时用Class不用带 * 号去指向是因为Class本质就是objc_class *而已。而本文中的bucket_t *它其实代表的是一个哈希链表结构,它是无序的。

LLDB进行探究cache缓存的方法
在这里不贴源码,只需知道一个类Person有一个实例方法叫testMethod。然后我们只需要断点Person *p = [Person alloc]的下一行。 注意本次LLDB 是在源码工程调试。
如果说需要在无源码环境下进行调试也是可以的,我们可以将这些结构体给写在自己工程中,既然我们已经知道了它内存的分布情况就可以直接强制转换就行了,到时候也可以打印输出其缓存的方法
1. 先看没有调用方法的cache结构

2. 然后LLDB 调用方法 进行查看
然后发现_maybeMask为7,而_occupied为1,这就证明了其实我们调用方法过后会有变化。大家请看注意事项
注意:如果源码进行调用方法的话其实 _occupied为1,而_maybeMask为3。这是因为我们在用LLDB进行调用方法的时候系统自动会调用三个方法所以_maybeMask为7。
- 没有源码调用方法结果

- 源码调用方法结果

3. 查看cache究竟缓存了什么方法
可以查看到它其实在第5个平移位置缓存了方法。正是我在bucket_t中说道它是哈希链表结构是无序的。其实它的存储方法的方式是根据哈希函数来的,是有迹可寻的

insert方法的探究
为什么要研究这个方法?
- 从这个方法中我们能获得缓存方法的存储数量 _ocuupied
- 我们能知道方法的容器大小_maybeMask是在什么时候进行扩容的,怎么扩容的
- 当我们每次缓存方法后它如果在3/4临界点会将旧的方法移除,然后新的方法插入。(为什么要将旧的方法移除,是因为苹果认为新的方法进来后其实旧的方法不那么重要,而苹果遵从越新越好)
- 只有方法插入到缓存以后,objc_msgSend才会更快的查找
源码
里面的重点就是:容器buckets的开辟存储,用哈希函数计算方法的插入位置,
void cache_t::insert(SEL sel, IMP imp, id receiver)
{
//runtime加个锁不用管
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.
//给缓存的方法数量进行赋值,第一次为0+1,后面的时候为x+1
mask_t newOccupied = occupied() + 1;
// 为 maybeMask, 即缓存方法锁开辟的空间数量,第一次为0
unsigned oldCapacity = capacity(), capacity = oldCapacity;
// 第一次进入的时候进该方法
if (slowpath(isConstantEmptyCache())) {
// Cache is read-only. Replace it.第一次进来的时候capacity = 4
if (!capacity) capacity = INIT_CACHE_SIZE;
// 进行开辟容器的空间,数量,false:不用释放旧的缓存方法
reallocate(oldCapacity, capacity, /* freeOld */false);
}
//当缓存的方法数量少于开辟的容器数量的3/4的时候。
else if (fastpath(newOccupied + CACHE_END_MARKER <= cache_fill_ratio(capacity))) {
// Cache is less than 3/4 or 7/8 full. Use it as-is.
}
//如果架构为__arm64__ && __LP64__ 的时候才走
#if CACHE_ALLOW_FULL_UTILIZATION
// 如果 capacity<=8 && newOccupied + (在一些架构中为0一些为1) <= capacity
else if (capacity <= FULL_UTILIZATION_CACHE_SIZE && newOccupied + CACHE_END_MARKER <= capacity) {
// Allow 100% cache utilization for small buckets. Use it as-is.
}
#endif
//一般是当容器大小的3/4倍 < 缓存方法数量
else {
//如果 capacity为0的时候 赋值为4,至于为什么最终结果为3是reallocate方法
capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
//容器最大数量为 1 << 16 = 0x1后面15个0, = 65536
if (capacity > MAX_CACHE_SIZE) {
capacity = MAX_CACHE_SIZE;
}
// 进行开辟容器的空间,数量,true:释放旧的缓存方法
reallocate(oldCapacity, capacity, true);
}
// 获取缓存方法的容器(哈希链表数组)
bucket_t *b = buckets();
// m 第一次为3
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.
do {
//如果说需要插入的方法不在缓存中
if (fastpath(b[i].sel() == 0)) {
// occupied + 1
incrementOccupied();
//将方法缓存
b[i].set<Atomic, Encoded>(b, 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 循环条件:如果有哈希冲突就用 再哈希法 解决冲突
} while (fastpath((i = cache_next(i, m)) != begin));
bad_cache(receiver, (SEL)sel);
#endif // !DEBUG_TASK_THREADS
}
复制代码
补充reallocate方法
void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld)
{
// 获取旧的容器
bucket_t *oldBuckets = buckets();
// 创建新的容器,容器的大小为 newCapacity
bucket_t *newBuckets = allocateBuckets(newCapacity);
// Cache's old contents are not propagated.
// This is thought to save cache memory at the cost of extra cache fills.
// fixme re-measure this
ASSERT(newCapacity > 0);
ASSERT((uintptr_t)(mask_t)(newCapacity-1) == newCapacity-1);
//将容器的buckets与maybeMask存储也就是 _bucketsAndMaybeMask和_maybeMask
// 如果是第一次进行申请,则容器大小为3
setBucketsAndMask(newBuckets, newCapacity - 1);
// 如果需要释放旧的容器的话进行释放
if (freeOld) {
collect_free(oldBuckets, oldCapacity);
}
}
复制代码




















![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)