前言算法
cache_t
結構體struct objc_class : objc_object {
// Class ISA; //8
Class superclass; //8
cache_t cache; //16 // formerly cache pointer and vtable
class_data_bits_t bits;
...省略部分信息...
};
複製代碼
cache_t
結構體struct cache_t {
struct bucket_t *_buckets; //8
mask_t _mask; //4 typedef uint32_t mask_t;
mask_t _occupied; //4
public:
struct bucket_t *buckets();
mask_t mask();
mask_t occupied();
void incrementOccupied();
void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
void initializeToEmpty();
mask_t capacity();
bool isConstantEmptyCache();
bool canBeFreed();
static size_t bytesForCapacity(uint32_t cap);
static struct bucket_t * endMarker(struct bucket_t *b, uint32_t cap);
void expand();
void reallocate(mask_t oldCapacity, mask_t newCapacity);
struct bucket_t * find(SEL sel, id receiver);
static void bad_cache(id receiver, SEL sel, Class isa) __attribute__((noreturn));
};
複製代碼
struct bucket_t *_buckets
struct bucket_t {
private:
#if __arm64__
uintptr_t _imp; //typedef unsigned long uintptr_t;
SEL _sel;
#else
SEL _sel;
uintptr_t _imp;
#endif
...省略部分信息...
};
複製代碼
_buckets
一個指向bucket_t
結構體的指針,能夠看命名是一個數組;_imp
函數實現地址;_sel
函數;- 注意在
arm64
的平臺上_imp
在_sel
的前面。
mask_t _mask
一個修飾的值,在接下來的分析能夠獲得是 hash鏈表的長度減1。數組
mask_t _occupied
緩存已佔用的空間緩存
cache_t
緩存的內容經過bucket_t
結構體觀察存的就是imp
和sel
,證明緩存的是OC
的方法。bash
cache_t
緩存的流程咱們在objc
的源碼objc_cache.mm
文件的開始位置能夠看到這麼一段註釋less
* Cache readers (PC-checked by collecting_in_critical())
* objc_msgSend*
* cache_getImp
*
* Cache writers (hold cacheUpdateLock while reading or writing; not PC-checked)
* cache_fill (acquires lock)
* cache_expand (only called from cache_fill)
* cache_create (only called from cache_expand)
* bcopy (only called from instrumented cache_expand)
* flush_caches (acquires lock)
* cache_flush (only called from cache_fill and flush_caches)
* cache_collect_free (only called from cache_expand and cache_flush)
複製代碼
既然是緩存就是要寫入,咱們直接從
Cache writers
下面看,cache_fill
就是緩存開始的位置。函數
cache_fill
void cache_fill(Class cls, SEL sel, IMP imp, id receiver)
{
#if !DEBUG_TASK_THREADS
mutex_locker_t lock(cacheUpdateLock);
cache_fill_nolock(cls, sel, imp, receiver);
#else
_collecting_in_critical();
return;
#endif
}
複製代碼
忽略其餘信息,只有
cache_fill_nolock
這個函數是咱們的目標。post
cache_fill_nolock
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 wasnot added to the cache by some other thread
// before we grabbed the cacheUpdateLock.
if (cache_getImp(cls, sel)) return;
cache_t *cache = getCache(cls);
// 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(sel, receiver);
if (bucket->sel() == 0) cache->incrementOccupied();
bucket->set<Atomic>(sel, imp);
}
複製代碼
cache_t
緩存的流程深刻從上面的分析咱們知道在函數cache_fill_nolock
裏面會實現緩存步驟。接下來開始對每一步深刻研究。ui
if (!cls->isInitialized()) return;
if (cache_getImp(cls, sel)) return;
複製代碼
- 判斷類是否已經初始化,尚未就直接返回了。
- 在類裏面是否能夠找到
sel
,能夠找到,說明已經緩存了,直接返回。
cache_t *cache = getCache(cls)
cache_t *getCache(Class cls)
{
assert(cls);
return &cls->cache;
}
複製代碼
從當前類獲取
cache
成員。this
mask_t newOccupied = cache->occupied() + 1
occupied()
函數返回的是_occupied
,即當前已經佔用的空間大小。_occupied
+ 1 , 表示新的即將佔用的內存空間大小。
mask_t capacity = cache->capacity()
mask_t cache_t::capacity()
{
return mask() ? mask()+1 : 0;
//mask() return _mask;
}
複製代碼
獲取緩存的容量,注意這裏 + 1,緣由是由於賦值的時候,用了容量 - 1。atom
if (cache->isConstantEmptyCache()) {
// Cache is read-only. Replace it.
//INIT_CACHE_SIZE 4
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();
}
複製代碼
cache
是空的,就須要reallocte
。newOccupied
小於或者等於總容量的3/4,就走後面的。- 若是大於3/4,就須要
expand()
即緩存擴容。
bucket
以及賦值bucket_t *bucket = cache->find(sel, receiver);
if (bucket->sel() == 0) cache->incrementOccupied();
bucket->set<Atomic>(sel, imp);
複製代碼
緩存的主線流程就是這樣的一個過程的,接下來把第5步和第6步進行分析。
容量處理過程
和查找過程
分析cache->isConstantEmptyCache()
bool cache_t::isConstantEmptyCache()
{
return
occupied() == 0 &&
buckets() == emptyBucketsForCapacity(capacity(), false);
}
複製代碼
- 第一次進來的時候
occupied()
返回值_occupoed
值爲0。buckets()
返回值_buckets
是一個尚未初始化的指針爲nil。
bucket_t *emptyBucketsForCapacity(mask_t capacity, bool allocate = true)
{
cacheUpdateLock.assertLocked();
size_t bytes = cache_t::bytesForCapacity(capacity);
// Use _objc_empty_cache if the buckets is small enough.
if (bytes <= EMPTY_BYTES) {
return (bucket_t *)&_objc_empty_cache;
}
*
走到這個就會直接返回了
bucket_t * 8 1000
_objc_empty_cache 16 10000
& 00000
*
//下面的代碼與咱們的流程沒有關係 暫不分析
// Use shared empty buckets allocated on the heap.
static bucket_t **emptyBucketsList = nil;
static mask_t emptyBucketsListCount = 0;
mask_t index = log2u(capacity);
if (index >= emptyBucketsListCount) {
if (!allocate) return nil;
mask_t newListCount = index + 1;
bucket_t *newBuckets = (bucket_t *)calloc(bytes, 1);
emptyBucketsList = (bucket_t**)
realloc(emptyBucketsList, newListCount * sizeof(bucket_t *));
// Share newBuckets for every un-allocated size smaller than index.
// The array is therefore always fully populated.
for (mask_t i = emptyBucketsListCount; i < newListCount; i++) {
emptyBucketsList[i] = newBuckets;
}
emptyBucketsListCount = newListCount;
if (PrintCaches) {
_objc_inform("CACHES: new empty buckets at %p (capacity %zu)",
newBuckets, (size_t)capacity);
}
}
return emptyBucketsList[index];
}
複製代碼
cache_t::bytesForCapacity(capacity)
,參數capacity
爲0,實現爲sizeof(bucket_t) * (cap + 1)
,bucket_t
結構體裏面有兩個成員都是8字節總共16字節,運算結果爲16。EMPTY_BYTES
,一個宏定義,在DEBUG
值爲(8+1)*16
,不然爲(1024+1)*16
。if (bytes <= EMPTY_BYTES)
顯然這個條件是知足的。(bucket_t *)&_objc_empty_cache
,這是一個&
運算,OBJC_EXPORT struct objc_cache _objc_empty_cache
這是_objc_empty_cache
的定義,它的字節佔用的值爲16,咱們在objc_cache
這個結構體裏面是能夠看到的。8
&16
記過計算出來爲0。- 在
if
條件裏面返回了,後面的流程就不用看了。
則第一次進來的時候這個條件判斷是否爲空的緩存是存在的。
cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE)
表達的意思爲緩存空的時候,就去開闢。
void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity)
{
bool freeOld = canBeFreed();
bucket_t *oldBuckets = buckets();
bucket_t *newBuckets = allocateBuckets(newCapacity);
// Cache is 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);
}
}
複製代碼
bool freeOld = canBeFreed()
,函數的實現就是!isConstantEmptyCache()
,則freeOld
的值爲true
。bucket_t *newBuckets = allocateBuckets(newCapacity)
,根據容量 系統開闢一個新的buckets
。setBucketsAndMask(newBuckets, newCapacity - 1)
。作了三件事,空的_buckets
賦值;_mask
賦值,注意這裏是容量 - 1;_occupied
已佔用的空間置爲空。if (freeOld)
經過上面的取值以及知道是成立的,主要作了清空舊的緩存。
newOccupied <= capacity / 4 * 3
這個意思就是新佔用的空間是否小於總容量的3/4。
cache->expand()
同比第7步,若是容量大於3/4的時候,內存須要擴容。
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 it grow further
// fixme this wastes one bit of mask
newCapacity = oldCapacity;
}
reallocate(oldCapacity, newCapacity);
}
複製代碼
uint32_t newCapacity = oldCapacity ? oldCapacity*2 : INIT_CACHE_SIZE;
,這裏擴容後的容量會是舊容量的兩倍。reallocate(oldCapacity, newCapacity)
,這一步如同剛開始沒有緩存的時候,從新讓系統來開闢。
bucket_t *bucket = cache->find(sel, receiver)
bucket_t * cache_t::find(SEL s, id receiver)
{
assert(s != 0);
bucket_t *b = buckets(); //獲取_buckets
mask_t m = mask(); //獲取_mask
mask_t begin = cache_hash(s, m);
mask_t i = begin;
do {
if (b[i].sel() == 0 || b[i].sel() == s) {
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)s, cls);
}
複製代碼
cache_hash(sel, _mask)
,這是一個hash
算法return (mask_t)(uintptr_t)sel & mask
,這裏也能夠知道_mask
會比總容量小1的緣由,就是這裏計算的時候保證不會越界。do...while
循環,當_sel
不存在或者在緩存裏面匹配到了,就返回當前的bucket
。循環的條件在arm64
下面是return i ? i-1 : mask;
,實際上就是造成一個閉環來查找。
if (bucket->sel() == 0) cache->incrementOccupied()
void cache_t::incrementOccupied()
{
_occupied++;
}
複製代碼
表示在緩存裏面沒有找到的時候,這個時候要存到緩存裏面,則已佔用的空間須要增長。
bucket->set<Atomic>(sel, imp)
void bucket_t::set(SEL newSel, IMP newImp)
{
_imp = (uintptr_t)newImp;
if (_sel != newSel) {
if (atomicity == Atomic) {
mega_barrier();
}
_sel = newSel;
}
}
複製代碼
這一步就是表示當前取出來的
bucket
來從新賦值_sel
和_imp
這兩個成員。
到此爲止cache
的緩存流程就結束了。