在objective-c语言中,对象调用方法以后,这个方法是会被缓存起来的。下次再调用这个方法的时候,直接从缓存里面去找,而不用再去遍历从类到父类再到祖宗类的方法列表了。本文就是从源码分析这个方法缓存的功能是如何实现的。objective-c
其实就是一个开放地址法的hash表数组
typedef struct objc_class *Class;
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
...
}复制代码
Class是指向objc_class的指针,objc_class内部存在一个 cache_t cache;cache就是用来缓存最近调用过的方法的。
缓存
struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
};复制代码
typedef unsigned long uintptr_t;
typedef uintptr_t cache_key_t;
struct bucket_t {
cache_key_t _key;
MethodCacheIMP _imp;
}复制代码
cache_key_t key = getKey(sel);
cache_key_t getKey(SEL sel)
{
assert(sel);
return (cache_key_t)sel;
}复制代码
先看缓存中是否已经存在了该方法,若是已经存在,直接return掉,不用再缓存安全
从class中拿到cache列表,将sel转换为内存地址。bash
将_key与_mask相与,由于_mask是数组大小-1,因此获得的结果恰好小于数组的大小。less
若是获得的位置已经被占用,则日后寻找,直到找到空的位置,把缓存设置到这个位置。oop
void cache_fill(Class cls, SEL sel, IMP imp, id receiver)
{
// 由于cache_t内部用来储存的结构其实就是个数组
// 因此操做的时候须要先加个锁,保证线程安全。
mutex_locker_t lock(cacheUpdateLock);
cache_fill_nolock(cls, sel, imp, receiver);
}
static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver)
{
cacheUpdateLock.assertLocked();
// Never cache before +initialize is done
if (!cls->isInitialized()) return;
// Make sure the entry wasn't added to the cache by some other thread
// before we grabbed the cacheUpdateLock.
// 若是缓存中已经缓存过了,不用再缓存,直接return
if (cache_getImp(cls, sel)) return;
cache_t *cache = getCache(cls);
cache_key_t key = getKey(sel);
// Use the cache as-is if it is less than 3/4 full
mask_t newOccupied = cache->occupied() + 1;
mask_t capacity = cache->capacity();
if (cache->isConstantEmptyCache()) {
// Cache is read-only. Replace it.
cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE);
}
else if (newOccupied <= capacity / 4 * 3) {
// Cache is less than 3/4 full. Use it as-is.
}
else {
// Cache is too full. Expand it.
cache->expand();
}
// 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.
bucket_t *bucket = cache->find(key, receiver);
if (bucket->key() == 0) cache->incrementOccupied();
bucket->set(key, imp);
}
复制代码
void cache_t::expand()
{
cacheUpdateLock.assertLocked();
uint32_t oldCapacity = capacity();
uint32_t newCapacity = oldCapacity ? oldCapacity*2 : INIT_CACHE_SIZE;
if ((uint32_t)(mask_t)newCapacity != newCapacity) {
// mask overflow - can't grow further // fixme this wastes one bit of mask newCapacity = oldCapacity; } reallocate(oldCapacity, newCapacity); } void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity) { bool freeOld = canBeFreed(); bucket_t *oldBuckets = buckets(); 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);
setBucketsAndMask(newBuckets, newCapacity - 1);
if (freeOld) {
cache_collect_free(oldBuckets, oldCapacity);
cache_collect(false);
}
}
bucket_t * cache_t::find(cache_key_t k, id receiver)
{
assert(k != 0);
bucket_t *b = buckets();
mask_t m = mask();
mask_t begin = cache_hash(k, m);
mask_t i = begin;
do {
if (b[i].key() == 0 || b[i].key() == k) {
return &b[i];
}
} while ((i = cache_next(i, m)) != begin);
// hack
Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
cache_t::bad_cache(receiver, (SEL)k, cls);
}复制代码
cache_t的取出操做为 cache_getImp(cls, sel) ,该代码是使用汇编语言编写的,好在旁边有注释。源码分析
cache_getImp 方法将参数cls 放到r10寄存器,而后调用了 CacheLookup方法
ui
STATIC_ENTRY _cache_getImp
// do lookup
movq %a1, %r10 // move class to r10 for CacheLookup
CacheLookup NORMAL, GETIMP // returns IMP on success复制代码
将sel放进r11寄存器,而后将 sel和cls->cache.mask相与的结果放进r11寄存器,找到key与现有的sel比较this
.macro CacheLookup
movq %a2, %r11 // r11 = _cmd
andl 24(%r10), %r11d // r11 = _cmd & class->cache.mask
shlq $$4, %r11 // r11 = offset = (_cmd & mask)<<4
addq 16(%r10), %r11 // r11 = class->cache.buckets + offset
cmpq cached_sel(%r11), %a2 // if (bucket->sel != _cmd)
jne 1f // scan more
// CacheHit must always be preceded by a not-taken `jne` instruction
CacheHit $0, $1 // call or return imp
1:
// loop
cmpq $$1, cached_sel(%r11)
jbe 3f // if (bucket->sel <= 1) wrap or miss
addq $$16, %r11 // bucket++
2:
cmpq cached_sel(%r11), %a2 // if (bucket->sel != _cmd)
jne 1b // scan more
// CacheHit must always be preceded by a not-taken `jne` instruction
CacheHit $0, $1 // call or return imp
3:
// wrap or miss
jb LCacheMiss_f // if (bucket->sel < 1) cache miss
// wrap
movq cached_imp(%r11), %r11 // bucket->imp is really first bucket
jmp 2f
// Clone scanning loop to miss instead of hang when cache is corrupt.
// The slow path may detect any corruption and halt later.
1:
// loop
cmpq $$1, cached_sel(%r11)
jbe 3f // if (bucket->sel <= 1) wrap or miss
addq $$16, %r11 // bucket++
2:
cmpq cached_sel(%r11), %a2 // if (bucket->sel != _cmd)
jne 1b // scan more
// CacheHit must always be preceded by a not-taken `jne` instruction
CacheHit $0, $1 // call or return imp
3:
// double wrap or miss
jmp LCacheMiss_f
.endmacro复制代码