- 我們都知道OC中
屬性
是存儲數(shù)據(jù)信息
的,方法
的功能修改屬性的數(shù)據(jù)
.- 在前面我們分析過
objc_class
結(jié)構(gòu)體(里面存儲類的信息), 里面有繼承過來的isa
(指向元類), 有superClass
, 有bits
(存儲屬性, 實例方法, 代理, ro里有成員變量)結(jié)構(gòu)體- 那
cache
結(jié)構(gòu)體里面存儲的是什么呢?
1: 我們先根據(jù)源碼梳理下objc_class的結(jié)構(gòu)圖
2: 接下來我們來通過指針偏移試著看看cache里存儲的是什么
關(guān)于緩存占用量的計算,有以下幾點說明:
-
buckets() 是個列表, 怎么查找多個呢? 圖是月月的!
- 利用指針偏移, 數(shù)組的
首地址
即第一個元素
的地址
例:
*($4 + 1)
- 利用列表特性
例:
$3.buckets()[1]
- 利用指針偏移, 數(shù)組的
經(jīng)過lldb打印查看, 我們可以確認(rèn)cache里存儲的是
方法緩存
alloc申請空間時,此時的對象已經(jīng)創(chuàng)建试吁,調(diào)用的
實例方法
邦鲫,都是shioccupied
+1, 擴(kuò)容后會清空buckets
,occupied
為置為0
當(dāng)有屬性賦值時,會隱式調(diào)用
set
方法剩膘,occupied
也會增加
3: 我們詳細(xì)的看下cache的結(jié)構(gòu)
struct cache_t {//只復(fù)制了部分重要信息
//CACHE_MASK_STORAGE_OUTLINED: 模擬器 or macOS環(huán)境
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
// explicit_atomic: 原子性, 保證cache增刪改差的線程安全
// 等同于struct bucket_t * _buckets;
// _buckets: 存放imp和sel
explicit_atomic<struct bucket_t *> _buckets;
explicit_atomic<mask_t> _mask; //掩碼
//CACHE_MASK_STORAGE_HIGH_16: 64位真機(jī)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
// 真機(jī)環(huán)境中, buckets和mask掩碼存儲在一起, 掩碼在高16位(通過 << maskShift), buckets 存在剩余位
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;//暫時沒有用到, 猜測沒開發(fā)完, 不管它
static constexpr uintptr_t maskShift = 48;
//掩碼后的其他位必須為零。 msgSend
//利用這些附加位來構(gòu)造值
//在一條來自_maskAndBuckets的指令中`mask << 4`。
static constexpr uintptr_t maskZeroBits = 4;
// The largest mask value we can store.
static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
// 應(yīng)用于`_maskAndBuckets`的掩碼既绩,以獲取存儲桶指針。
static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << (maskShift - maskZeroBits)) - 1;
// 確保我們有足夠的位用于存儲桶指針还惠。
static_assert(bucketsMask >= MACH_VM_MAX_ADDRESS, "Bucket field doesn't have enough bits for arbitrary pointers.");
//非64為真機(jī), 因為iOS9之后廢棄32位, 所以我們不研究它
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
// _maskAndBuckets stores the mask shift in the low 4 bits, and
// the buckets pointer in the remainder of the value. The mask
// shift is the value where (0xffff >> shift) produces the correct
// mask. This is equal to 16 - log2(cache_size).
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
static constexpr uintptr_t maskBits = 4;
static constexpr uintptr_t maskMask = (1 << maskBits) - 1;
static constexpr uintptr_t bucketsMask = ~maskMask;
#else
#error Unknown cache mask storage type.
#endif
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
public:
static bucket_t *emptyBuckets();
//重點可以獲取buckets列表
struct bucket_t *buckets();
mask_t mask();// 獲取我們的掩碼(也可以理解為開辟最大空間)
mask_t occupied();// 記錄當(dāng)前緩存的方法數(shù)量
void incrementOccupied();// 操作`occupied++`, 即新插入一個bucket
void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
void initializeToEmpty();
unsigned capacity();// 容量
static struct bucket_t * endMarker(struct bucket_t *b, uint32_t cap);
// 重新開辟空間, 一般在內(nèi)存滿3/4時擴(kuò)容后調(diào)用
void reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld);
// 插入新的bucket
void insert(Class cls, SEL sel, IMP imp, id receiver);
4: 總結(jié): 我們來梳理下cache的工作流程
疑問解答 --Style_月月
-
1饲握、
_mask
是什么?_mask
是指掩碼數(shù)據(jù)蚕键,用于在哈希算法或者哈希沖突算法(cache_next)中計算哈希 下標(biāo)救欧,其中mask
等于capacity
(內(nèi)存總?cè)萘? - 1 -
2、
_occupied
是什么锣光?_occupied
表示哈希表中sel-imp
的占用大小 (即可以理解為分配的內(nèi)存中已經(jīng)存儲了sel-imp
的的個數(shù))笆怠,alloc
后, 調(diào)用的實例方法
都會導(dǎo)致occupied
變化, 包括屬性隱式實現(xiàn)的set方法
-
3、為什么隨著方法調(diào)用的增多嫉晶,其打印的occupied 和 mask會變化骑疆?
因為在
cache第一次
緩存bucket時,分配的空間是4
個替废,隨著方法調(diào)用的增多箍铭,當(dāng)存儲的bucket
個數(shù)超過
capacity
(總?cè)萘?的3/4
, 就會進(jìn)行capacity
翻倍, 并清理舊緩存
, 之后繼續(xù)緩存新調(diào)用的實例方法
. -
4、bucket數(shù)據(jù)為什么會有丟失的情況椎镣?诈火,例如2-7中,只有say3状答、say4方法有函數(shù)指針
原因是在擴(kuò)容時冷守,是將原有的內(nèi)存全部清除了刀崖,再重新申請了內(nèi)存導(dǎo)致的, 見
疑問3
解答 -
5、2-7中say3拍摇、say4的打印順序為什么是say4先打印亮钦,say3后打印,且還是挨著的充活,即順序有問題蜂莉?
因為
bucket
的存儲是通過哈希算法-cache_hash
計算下標(biāo)的,其計算的下標(biāo)有可能已經(jīng)存儲了sel混卵,所以又需要通過哈希沖突-cache_next
算法重新計算哈希下標(biāo)映穗,所以下標(biāo)并不是固定
的
cache_hash實現(xiàn)-c739