1梅尤、回顧
在程序運(yùn)行的時(shí)候柜思,oc對(duì)象在內(nèi)存中的存儲(chǔ)結(jié)構(gòu)是objc_class類(lèi)型的,objc_class存放著類(lèi)的方法列表巷燥,屬性列表赡盘,協(xié)議列表,成員變量列表
還存放著方法的緩存列表
2缰揪、方法緩存列表
思考:為什么要?jiǎng)?chuàng)建方法緩存列表陨享,目的是什么
2.1 方法的調(diào)用
假如調(diào)用對(duì)象方法的時(shí)候,首先從對(duì)象的方法列表中查詢(xún)方法钝腺,如果不存在抛姑,通過(guò)superclass查找父類(lèi)的方法列表,直到找到方法艳狐。
按照這樣的邏輯執(zhí)行的話(huà)定硝,每次調(diào)用方法都會(huì)去查詢(xún)方法,這樣會(huì)造成資源的浪費(fèi)
加入存在一個(gè)方法的緩存列表毫目,第一次調(diào)用方法的時(shí)候蔬啡,都將這個(gè)方法緩存起來(lái),那么下次再調(diào)用這個(gè)方法的時(shí)候镀虐,就可以直接從緩存列表中讀取箱蟆,不需要再按照流程去查詢(xún)方法
再次調(diào)用使用過(guò)的方法的時(shí)候,直接從緩存列表中讀取
3粉私、緩存列表的內(nèi)部結(jié)構(gòu) cache_t
// cache_t 內(nèi)部主要有三個(gè)屬性
struct cache_t {
struct bucket_t *_buckets; // 是一個(gè)數(shù)組 散列表
mask_t _mask; // 散列表的長(zhǎng)度 - 1顽腾, 散列表的長(zhǎng)度 >= 已經(jīng)緩存的數(shù)量
mask_t _occupied; // 已經(jīng)緩存的方法數(shù)量
}
struct bucket_t {
private:
#if __arm64__ // 手機(jī)
SEL _sel; // SEL作為key
uintptr_t _imp; // 函數(shù)的地址作為value
#else
SEL _sel; // SEL作為key
uintptr_t _imp; // 函數(shù)的地址作為value
#endif
}
public:
// 獲取sel
inline SEL sel() const { return _sel; }
// 獲取imp
inline IMP imp() const {
if (!_imp) return nil;
return (IMP)
ptrauth_auth_and_resign((const void *)_imp,
ptrauth_key_process_dependent_code,
modifierForSEL(_sel),
ptrauth_key_function_pointer, 0);
}
template <Atomicity>
// 賦值 sel作為key imp作為value
void set(SEL newSel, IMP newImp);
};
4、存儲(chǔ)與查詢(xún)方式
/// 查詢(xún)方法的方式
//通過(guò)sel 方法名
bucket_t * cache_t::find(SEL s, id receiver)
{
assert(s != 0);
// 1.獲取散列表
bucket_t *b = buckets();
// 2.獲取_mask 散列表的長(zhǎng)度 - 1
mask_t m = mask();
// 3.方法名作為key ,cache_hash 內(nèi)部是將sel & mask 獲取位置下標(biāo)
mask_t begin = cache_hash(s, m);
// 4. i 存放的位置
mask_t i = begin;
do {
///如果 為空 或方法相同抄肖,返回
if (b[i].sel() == 0 || b[i].sel() == s) {
return &b[I];
}
// 如果方法不一致久信,將i -1 向上一個(gè)位置查詢(xún),直到返回結(jié)果
} 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);
}
通過(guò)源碼分析發(fā)現(xiàn)漓摩,蘋(píng)果不是單純的通過(guò)遍歷的方式查詢(xún)方法的位置裙士。 而是通過(guò)將 sel 與 mask進(jìn)行 &預(yù)算計(jì)算出 方法存放的下標(biāo),如果下標(biāo)內(nèi)已經(jīng)存放了方法管毙,會(huì)繼續(xù)往上一個(gè)位置存放
如果散列表中分配的內(nèi)存已經(jīng)存滿(mǎn)腿椎,會(huì)重新分配
void cache_t::expand()
{
cacheUpdateLock.assertLocked();
// 舊的數(shù)量
uint32_t oldCapacity = capacity();
// 新的數(shù)量 擴(kuò)容2倍
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);
}
如果緩存列表滿(mǎn)了啃炸,會(huì)清空緩存列表,擴(kuò)容內(nèi)存為原來(lái)的兩倍卓舵,等待下次調(diào)用方法的時(shí)候重新進(jìn)行緩存南用,因?yàn)?_mask 存放的數(shù)量 已經(jīng)改變了,進(jìn)行 &運(yùn)算的時(shí)候掏湾,肯定是和之前的值不一樣裹虫,所以需要清空,重新緩存
5融击、總結(jié)
方法緩存的存儲(chǔ)方式 為散列表存儲(chǔ)
1.通過(guò)方法名 & 當(dāng)前的_mask 的數(shù)量 計(jì)算出下標(biāo)
2.將方法存放到下標(biāo)位置
3.如果當(dāng)前位置已經(jīng)存儲(chǔ)了別的方法筑公,那么將下標(biāo)-1,向上存儲(chǔ)
4.如果第一個(gè)位置也存儲(chǔ)了尊浪,就沖最后一個(gè)位置繼續(xù)
5.如果全部都滿(mǎn)了匣屡,就會(huì)重新分配內(nèi)存,重新進(jìn)行緩存