在 類結構分析中 知道了屬性和方法是如何獲取的,可是爲了看 class_data_bits_t bits;
中的內容,僅僅計算了 cache_t cache;
的大小,並無認真的瞭解和研究過 cache_t
,當前內容就是對 Cache_t
的分析。緩存
cache_t
是對 OC
程序中使用過方法的緩存,以便於下次調用方便。每次調用方法以前都須要先從 cache_t
中查找,若是有緩存,就不須要通過漫長的方法查找而直接調用了,若是沒有就給 cache_t
中緩存一份。bash
struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
//...
}
struct bucket_t {
private:
// IMP-first is better for arm64e ptrauth and no worse for arm64.
// SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
uintptr_t _imp;
SEL _sel;
#else
SEL _sel;
uintptr_t _imp;
#endif
//...
}
複製代碼
上方代碼就是 struct cache_t
中全部的成員,結構體中 _buckets
是 bucket_t *
的結構體指針類型, 存儲的是緩存方法的 SEL
方法編號和 IMP
方法實現的指針地址;_mask
表明的是當前能緩存的方法的個數;_occupied
表明的是當前已經緩存的方法數。less
既然知道 cache_t
成員所表明的含義,那如今經過 lldb
調試一下,main.m
文件中,寫入下方代碼。post
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
TestClass *object = [TestClass alloc];
Class pClass = object_getClass(object);
NSLog(@"%@ - %p",object,pClass);
}
return 0;
}
複製代碼
lldb 調試結果以下:ui
在類結構分析中 中說過:執行 x/4gx
後,第一個 8 字節表明 isa
,第二個 8 字節表明 superClass
,因此首地址偏移 16 個字節就是 cache_t
的首地址,一系列的取值後發現是空的,沒有緩存,那明明調用了 alloc
方法了啊,就算 alloc
方法特殊,可是也是方法啊,怎麼沒有緩存呢?this
緣由是:alloc
是類方法,類方法存儲在當前類的元類裏面,類裏面存儲的是實例方法。spa
明白了上述緣由,嘗試添加實例方法再次打印。線程
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
TestClass *object = [TestClass alloc];
Class pClass = object_getClass(object);
[object testClassInstanceMethod];
NSLog(@"%@ - %p",object,pClass);
}
return 0;
}
複製代碼
打印結果以下:3d
上方截圖打印出來發現,_mask
爲 3,_occupied
爲 1,打印出了上方代碼調用過的 testClassInstanceMethod
了,發現確實類的 cache_t
只是緩存的類的實例方法,而且調用一次後就會緩存。指針
cache_t
每次都使用上方 lldb
都累的夠嗆,當前使用一個自定義的方式去打印一下。自定義代碼以下:
typedef unsigned long uintptr_t;
typedef uint32_t mask_t;
struct custom_bucket_t {
#if __arm64__
uintptr_t _imp;
SEL _sel;
#else
SEL _sel;
uintptr_t _imp;
#endif
};
struct cache_t {
struct custom_bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
};
struct custom_class_data_bits_t {
// Values are the FAST_ flags above.
uintptr_t bits;
};
struct custom_objc_class {
Class ISA;
Class superclass;
struct cache_t cache; // formerly cache pointer and vtable
struct custom_class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
TestClass *object = [TestClass alloc];
Class pClass = object_getClass(object);
[object testClassInstanceMethod];
[object testClassInstanceMethod_1];
[object testClassInstanceMethod_2];
struct custom_objc_class *custom_pClass = (__bridge struct custom_objc_class *)(pClass);
for (mask_t i = 0; i < custom_pClass->cache._mask; i++) {
struct custom_bucket_t bucket = custom_pClass->cache._buckets[i];
NSLog(@"%s ---- %lu",bucket._sel,bucket._imp);
}
NSLog(@"結束打印:%@ - %p",object,pClass);
}
return 0;
}
複製代碼
打印結果以下:
這樣就能很清晰的看到 cache_t
中的緩存方法。
cache_t
上方代碼調用了3個實例方法,那咱們調用4個或者多個呢?上方代碼再添加一個調用 [object testClassInstanceMethod_3];
打印結果以下:
發現居然只有一個緩存方法打印了,其餘的呢?還有這裏調用 4 個方法的打印了 7 條,上個調用 3 個方法的就打印了 3 條呢?
帶着疑問接着看 cache_t
, 以前都是隻看了成員是什麼?成員的含義是什麼?可是沒有看過這些值是怎麼賦值的吧。
隨意個 cache_t
的 mask_t cache_t::mask()
方法打個斷點 ,而後再在咱們調用方法前打個斷點,先讓程序走到咱們調用方法以前,不然就會不斷的進入系統方法的調用。
程序進入了 mask()
的斷點,看下調用隊棧信息:
從上方隊棧信息能看到,先開始查找方法,而後緩存方法,這裏只關注方法緩存,看到關於緩存的字樣 cache_fill()
繼續上述操做,斷點進入 cache_fill()
,再下一步進入 cache_fill_nolock()
,發現此時纔是當前 cache_t
的重頭戲。
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
}
複製代碼
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 was not added to the cache by some other thread
// before we grabbed the cacheUpdateLock.
//確保在獲取cacheUpdateLock以前,條目沒有被其餘線程添加到緩存中。
//若是緩存中找到了,就 return 什麼也不須要幹
if (cache_getImp(cls, sel)) return;
//取出類對象中的 cache_t
cache_t *cache = getCache(cls);
// Use the cache as-is if it is less than 3/4 full
//若是緩存不足3/4,則按原樣使用緩存
//獲取當前保存的緩存已經使用的大小,而後進行 + 1 ,由於當前須要緩存新的。
mask_t newOccupied = cache->occupied() + 1;
//獲取當前 cache 的最大容量 _mask + 1
mask_t capacity = cache->capacity();
//若是是一個只讀的空緩存就進入
if (cache->isConstantEmptyCache()) {
// Cache is read-only. Replace it.
//緩存是隻讀的。取代它
//開闢一個緩存空間 capacity > 0 ? capacity : 4
// INIT_CACHE_SIZE : (1 << INIT_CACHE_SIZE_LOG2) -----> 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.
//緩存小於3/4滿。按原樣使用它。
}
else {
// Cache is too full. Expand it.
//超出了緩存大小的 4分之3 就須要 擴容
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.
//掃描第一個未使用的插槽並插入。
//保證有一個空槽,由於最小尺寸是4,咱們調整了3/4滿的尺寸。
//哈希去查找
bucket_t *bucket = cache->find(sel, receiver);
if (bucket->sel() == 0) {
// 若是 _sel 沒找到
/**
* incrementOccupied() {
* //當前已經緩存的方法總數+1;
* _occupied++;
* }
*/
cache->incrementOccupied();
}
//將當前要調用方法緩存
bucket->set<Atomic>(sel, imp);
}
複製代碼
void cache_t::expand()
{
//鎖
cacheUpdateLock.assertLocked();
//獲取原來緩存的最大容量
uint32_t oldCapacity = capacity();
//若是原來緩存的最大容量 > 0,就在原來的容量上 * 2,不然就返回 4
uint32_t newCapacity = oldCapacity ? oldCapacity*2 : INIT_CACHE_SIZE;
//若是新容量不等於舊容量就賦值
if ((uint32_t)(mask_t)newCapacity != newCapacity) {
// mask overflow - can not 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 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);
//給cache 從新賦值緩存池 _buckets,能存儲的_mask = newCapacity - 1 的值,而後把當前已經緩存的方法個數置爲0
setBucketsAndMask(newBuckets, newCapacity - 1);
//若是能夠釋放就釋放掉舊的緩存池
if (freeOld) {
cache_collect_free(oldBuckets, oldCapacity);
cache_collect(false);
}
}
複製代碼
以上就是對 cache_t
的緩存方法的流程了。