我們?cè)?a href="http://www.reibang.com/p/833e70929642" target="_blank">iOS底層之類的結(jié)構(gòu)分析分析了類的內(nèi)部結(jié)構(gòu)挣磨,而類的C/C++底層實(shí)際是objc_class
結(jié)構(gòu)體榕暇,其中包含了4個(gè)成員。
那么這一節(jié)我們來(lái)探究
cache_t
——類的方法緩存的真面目鸯绿,存儲(chǔ)了什么坐搔,怎么存儲(chǔ)的杭煎。
cache_t的結(jié)構(gòu)
我們從cache_t的源碼看:
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets;
explicit_atomic<mask_t> _mask;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
}
#endif
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
// ......
//省略了其他靜態(tài)成員和方法
這里的系統(tǒng)架構(gòu)的判斷條件宏定義是:
#define CACHE_MASK_STORAGE_OUTLINED 1
#define CACHE_MASK_STORAGE_HIGH_16 2
#define CACHE_MASK_STORAGE_LOW_4 3
#if defined(__arm64__) && __LP64__
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16
#elif defined(__arm64__) && !__LP64__
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_LOW_4
#else
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_OUTLINED
#endif
可以看到
CACHE_MASK_STORAGE_HIGH_16
——真機(jī)64
位架構(gòu)
CACHE_MASK_STORAGE_LOW_4
——真機(jī)非64
位架構(gòu)
CACHE_MASK_STORAGE_OUTLINED
——非真機(jī)即macOS
環(huán)境
在macOS
環(huán)境和真機(jī)64
位環(huán)境下纽哥,它們的區(qū)別是:
cache_t
的成員主要包括了:
-
macOS
環(huán)境下:
_buckets
:桶,包含了sel
方法編號(hào)代箭、imp
函數(shù)指針地址墩划。原子安全的。
_mask
:掩碼嗡综。原子安全的乙帮。 -
64
位真機(jī)環(huán)境下 :
_maskAndBuckets
:將_mask
和_buckets
放在一起。也就是存儲(chǔ)_mask
&_buckets
的結(jié)果极景。原子安全的察净。
_mask_unused
:未使用的掩碼,全局搜索后發(fā)現(xiàn)戴陡,這個(gè)成員并沒有被使用塞绿,猜測(cè)可能是沒開源出來(lái)或者是這部分還未寫完。
公共成員:
_flags
:位置標(biāo)識(shí)恤批。
_occupied
:已占用的异吻。
1.成員bucket_t
截取了部分bucket_t
的定義:
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__
explicit_atomic<uintptr_t> _imp;
explicit_atomic<SEL> _sel;
#else
explicit_atomic<SEL> _sel;
explicit_atomic<uintptr_t> _imp;
#endif
}
可以看到存儲(chǔ)了_imp
和_sel
,它們都是原子安全的喜庞,區(qū)別是诀浪,它們的順序不一樣。在arm64
位環(huán)境下延都,imp
放在前面對(duì)arm64e
架構(gòu)的ptrauth
更好雷猪,而其他環(huán)境下,sel放在前面對(duì)armv7*
晰房、i386
求摇、 x86_64
架構(gòu)更優(yōu)。
那么我們知道了bucket_t
的結(jié)構(gòu)殊者,就可以通過項(xiàng)目去查看下我們實(shí)例的對(duì)象的方法編號(hào)和實(shí)現(xiàn)了与境。
定義一個(gè)類,包括屬性和實(shí)例方法猖吴、類方法摔刁。
@interface BKPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSString *nickName;
- (void)sayHello;
- (void)sayWorld;
- (void)sayMaster;
- (void)sayNB;
+ (void)sayHappy;
@end
@implementation BKPerson
- (void)sayHello{
NSLog(@"BKPerson say : %s",__func__);
}
- (void)sayWorld{
NSLog(@"BKPerson say : %s",__func__);
}
- (void)sayMaster{
NSLog(@"BKPerson say : %s",__func__);
}
- (void)sayNB{
NSLog(@"BKPerson say : %s",__func__);
}
+ (void)sayHappy{
NSLog(@"BKPerson say : %s",__func__);
}
在main.m文件中
int main(int argc, const char * argv[]) {
@autoreleasepool {
BKPerson *p = [BKPerson alloc];
Class pClass = [BKPerson class];
[p sayHello];
[p sayWorld];
[p sayMaster];
NSLog(@"%@",pClass);
}
return 0;
}
在[p sayHello];
設(shè)置一個(gè)斷點(diǎn),運(yùn)行海蔽,并打印調(diào)試BKPerson
類的cache_t
信息:
這一步通過打印類的內(nèi)存首地址共屈,偏移16
個(gè)字節(jié)(isa
和superclass
成員各占了8
字節(jié))后得到的內(nèi)存位置就是cache_t
的信息绑谣。打印可以看到,這時(shí)候只初始化了類的實(shí)例對(duì)象拗引,還沒有執(zhí)行實(shí)例方法借宵。_buckets
里的_sel=null,_imp=0
寺擂,而且_mask=0暇务,_occupied=0
。
我們將斷點(diǎn)跳到下一行代碼怔软。再打印cache_t
:
可以看到這時(shí)候的
cache_t
里面是:_buckets
里的_sel=""垦细,_imp=11928
,而且_mask=3挡逼,_occupied=1
括改。而這個(gè)變數(shù)是因?yàn)槲覀儓?zhí)行了sayHello
這個(gè)方法。
難道真的是因?yàn)閳?zhí)行了sayHello
嗎家坎,我不信嘱能。
為了驗(yàn)證這一點(diǎn),那么可以打印下_buckets
的信息:
??這里要注意的是:如果直接使用打印的信息里的成員去打印虱疏,像圖上那樣的惹骂,是會(huì)取不到的。這時(shí)候我們可以查看cache_t
的定義做瞪,看看有沒有相關(guān)的方法对粪。
struct cache_t {
struct bucket_t *buckets();
mask_t mask();
mask_t occupied();
}
可以找到以上方法,而如果要打印sel
和imp
装蓬,則需要查看bucket_t
的定義:
struct bucket_t {
inline SEL sel() const { return _sel.load(memory_order::memory_order_relaxed); }
inline IMP imp(Class cls) const {
uintptr_t imp = _imp.load(memory_order::memory_order_relaxed);
if (!imp) return nil;
#if CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_PTRAUTH
SEL sel = _sel.load(memory_order::memory_order_relaxed);
return (IMP)
ptrauth_auth_and_resign((const void *)imp,
ptrauth_key_process_dependent_code,
modifierForSEL(sel, cls),
ptrauth_key_function_pointer, 0);
#elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_ISA_XOR
return (IMP)(imp ^ (uintptr_t)cls);
#elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_NONE
return (IMP)imp;
#else
#error Unknown method cache IMP encoding.
#endif
}
}
這時(shí)再通過以上方法打又谩:
可以百分百確定,由于執(zhí)行到了sayHello
方法牍帚,所以cache_t
存儲(chǔ)的信息發(fā)生了改變儡遮。
進(jìn)一步驗(yàn)證,我們把斷點(diǎn)跳到下一行暗赶。執(zhí)行了sayWorld
方法鄙币。
可以看到這時(shí)的_occupied=2
,但是我們繼續(xù)用之前的方式打印sel
和imp
蹂随,卻還是只能打印出sayHello
爱榔,而sayWorld
沒看到,但是通過成員名_buckets
可以確定這是一個(gè)集合類型糙及。
那么問題是,如何打印出_buckets
這個(gè)集合里的所有元素筛欢?
這里有兩種方式:
第一種:使用數(shù)組下標(biāo)的方式訪問元素
既然我們知道
buckets
是個(gè)數(shù)組浸锨,那么可以用下標(biāo)方式唇聘,取出第0
個(gè)、第1
個(gè)元素柱搜,而打印第1
個(gè)元素迟郎,可以得到sayWorld
的信息。
第二種:使用地址偏移聪蘸,訪問集合元素
我們知道宪肖,對(duì)于一個(gè)數(shù)組,我們可以將
數(shù)組指針+下標(biāo)數(shù)
健爬,之后對(duì)其*
取值控乾,得出元素。比如娜遵,這里sayWorld是第二個(gè)元素蜕衡,則對(duì)buckets
數(shù)組指針,進(jìn)行+1
设拟,之后用*
整體取值慨仿,可以得出sayWorld
元素。
可以發(fā)現(xiàn)纳胧,我們現(xiàn)在是在源碼環(huán)境镰吆,而每次打印值都需要$
取變量,很麻煩跑慕。那我們能在日常開發(fā)環(huán)境中進(jìn)行調(diào)試嗎万皿?
答案是肯定的。
脫離源碼環(huán)境相赁,我們可以模擬底層的類型結(jié)構(gòu)相寇,一樣可以調(diào)試。
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
struct my_bucket_t {
SEL _sel;
IMP _imp;
};
struct my_cache_t {
struct my_bucket_t * _buckets;
mask_t _mask;
uint16_t _flags;
uint16_t _occupied;
};
struct my_class_data_bits_t {
uintptr_t bits;
};
struct my_objc_class {
Class ISA;
Class superclass;
struct my_cache_t cache; // formerly cache pointer and vtable
struct my_class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
};
我們只需要取我們需要的成員钮科,繼承的關(guān)系也可以不要了唤衫。而這里需要注意的是:由于objc_class
是繼承于objc_object
,也就繼承了isa
成員绵脯,所以我們?nèi)サ袅死^承的關(guān)系后佳励,就要加上isa
成員。否則蛆挫,在打印類的cache_t
的相關(guān)信息時(shí)赃承,就會(huì)打印不到正確的值。
之后就可以打印類的方法sel
和imp
了悴侵。
int main(int argc, const char * argv[]) {
@autoreleasepool {
BKPerson *p = [BKPerson alloc];
Class pClass = [BKPerson class]; // objc_clas
[p eat1];
[p eat2];
[p eat3];
[p eat4];
struct my_objc_class *my_pClass = (__bridge struct my_objc_class *)(pClass);
NSLog(@"%hu - %u",my_pClass->cache._occupied,my_pClass->cache._mask);
for (mask_t i = 0; i<my_pClass->cache._mask; i++) {
// 打印獲取的 bucket
struct my_bucket_t bucket = my_pClass->cache._buckets[I];
NSLog(@"%@ - %p",NSStringFromSelector(bucket._sel),bucket._imp);
}
NSLog(@"Hello, World!");
}
return 0;
}
但是當(dāng)我們只執(zhí)行eat1
和eat2
瞧剖,和執(zhí)行eat1~eat4
,對(duì)比打印信息,可以看到:
可以看到_occupied
都是2
抓于,而_mask
從2
變成7
做粤,而且后一張圖的buckets
里打印的eat1
和eat2
不見了,而且eat3
和eat4
的順序捉撮,是錯(cuò)亂的怕品。
提出問題:_occupied
和_mask
是什么,為什么_occupied
都是2
巾遭,_mask
從2
變成7
肉康,為什么有些方法缺失了,為什么順序混亂灼舍?
可以知道吼和,產(chǎn)生變化是由于函數(shù)的調(diào)用,而非成員屬性發(fā)生變化片仿。
那么從源碼查找cache_t
纹安,主要包含了以下的函數(shù):
struct cache_t {
static bucket_t *emptyBuckets();
struct bucket_t *buckets();
mask_t mask();
mask_t occupied();
void incrementOccupied();
void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
void initializeToEmpty();
unsigned capacity();
bool isConstantEmptyCache();
bool canBeFreed();
}
點(diǎn)入occupied()
mask_t cache_t::occupied()
{
return _occupied;
}
void cache_t::incrementOccupied()
{
_occupied++;
}
可以看到occupied()
是一個(gè)get
方法,incrementOccupied()
是對(duì)_occupied
的遞增操作砂豌。這就是重點(diǎn)所在厢岂。
全局搜索incrementOccupied()
的調(diào)用,可以看到在insert
函數(shù)里調(diào)用了:
ALWAYS_INLINE
void cache_t::insert(Class cls, SEL sel, IMP imp, id receiver)
{
#if CONFIG_USE_CACHE_LOCK
cacheUpdateLock.assertLocked();
#else
runtimeLock.assertLocked();
#endif
ASSERT(sel != 0 && cls->isInitialized());
// Use the cache as-is if it is less than 3/4 full
mask_t newOccupied = occupied() + 1;
unsigned oldCapacity = capacity(), capacity = oldCapacity;
if (slowpath(isConstantEmptyCache())) {
// Cache is read-only. Replace it.
if (!capacity) capacity = INIT_CACHE_SIZE;
reallocate(oldCapacity, capacity, /* freeOld */false);
}
else if (fastpath(newOccupied + CACHE_END_MARKER <= capacity / 4 * 3)) { // 4 3 + 1 bucket cache_t
// Cache is less than 3/4 full. Use it as-is.
}
else {
capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE; // 擴(kuò)容兩倍 4
if (capacity > MAX_CACHE_SIZE) {
capacity = MAX_CACHE_SIZE;
}
reallocate(oldCapacity, capacity, true); // 內(nèi)存 擴(kuò)容完畢
}
bucket_t *b = buckets();
mask_t m = capacity - 1;
mask_t begin = cache_hash(sel, m);
mask_t i = begin;
// 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.
do {
if (fastpath(b[i].sel() == 0)) {
incrementOccupied();
b[i].set<Atomic, Encoded>(sel, imp, cls);
return;
}
if (b[i].sel() == sel) {
// The entry was added to the cache by some other thread
// before we grabbed the cacheUpdateLock.
return;
}
} while (fastpath((i = cache_next(i, m)) != begin));
cache_t::bad_cache(receiver, (SEL)sel, cls);
}
insert
分析
mask_t newOccupied = occupied() + 1;
:對(duì)occupied()
加一阳距。
if (slowpath(isConstantEmptyCache())) {
// Cache is read-only. Replace it.
if (!capacity) capacity = INIT_CACHE_SIZE;
reallocate(oldCapacity, capacity, /* freeOld */false);
}
else if (fastpath(newOccupied + CACHE_END_MARKER <= capacity / 4 * 3)) { // 4 3 + 1 bucket cache_t
// Cache is less than 3/4 full. Use it as-is.
}
else {
capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE; // 擴(kuò)容兩倍 4
if (capacity > MAX_CACHE_SIZE) {
capacity = MAX_CACHE_SIZE;
}
reallocate(oldCapacity, capacity, true); // 內(nèi)存 擴(kuò)容完畢
}
這一步有三個(gè)條件跳轉(zhuǎn)語(yǔ)句:
1. 如果是初始化:
由于初始化的情況比較少塔粒,所以系統(tǒng)使用slowpath
修飾條件以加快指令跳轉(zhuǎn),當(dāng)isConstantEmptyCache()
緩存為空的時(shí)候筐摘,也就是剛初始化時(shí)卒茬,if (!capacity) capacity = INIT_CACHE_SIZE;
,給capacity
賦初值為4
咖熟。
INIT_CACHE_SIZE_LOG2 = 2,
INIT_CACHE_SIZE = (1 << INIT_CACHE_SIZE_LOG2),
從上面宏知道INIT_CACHE_SIZE
的值為4圃酵。
之后執(zhí)行reallocate(oldCapacity, capacity, /* freeOld */false);
重新梳理。
ALWAYS_INLINE
void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld)
{
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);
}
}
reallocate
這個(gè)函數(shù)里主要做的是:根據(jù)newCapacity
開辟buckets
內(nèi)存馍管,而setBucketsAndMask
函數(shù)定義
void cache_t::setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask)
{
// objc_msgSend uses mask and buckets with no locks.
// It is safe for objc_msgSend to see new buckets but old mask.
// (It will get a cache miss but not overrun the buckets' bounds).
// It is unsafe for objc_msgSend to see old buckets and new mask.
// Therefore we write new buckets, wait a lot, then write new mask.
// objc_msgSend reads mask first, then buckets.
#ifdef __arm__
// ensure other threads see buckets contents before buckets pointer
mega_barrier();
_buckets.store(newBuckets, memory_order::memory_order_relaxed);
// ensure other threads see new buckets before new mask
mega_barrier();
_mask.store(newMask, memory_order::memory_order_relaxed);
_occupied = 0;
#elif __x86_64__ || i386
// ensure other threads see buckets contents before buckets pointer
_buckets.store(newBuckets, memory_order::memory_order_release);
// ensure other threads see new buckets before new mask
_mask.store(newMask, memory_order::memory_order_release);
_occupied = 0;
#else
#error Don't know how to do setBucketsAndMask on this architecture.
#endif
}
做了初始化的工作:根據(jù)_buckets
和_mask
進(jìn)行內(nèi)存初始化工作郭赐,_occupied
設(shè)置為0
。
2. 如果小于capacity
的3/4
時(shí)
fastpath(newOccupied + CACHE_END_MARKER <= capacity / 4 * 3)
#define CACHE_END_MARKER 1
可以看到這個(gè)宏定義為1确沸,也就是fastpath
大部分情況下是這個(gè)條件newOccupied+1<= capacity的3/4
捌锭,則不執(zhí)行任何語(yǔ)句。
3. 其他情況:也就是newOccupied+1>capacity的3/4
這時(shí)候則需要內(nèi)存擴(kuò)容:
capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
,如果capacity
等于0
罗捎,則給初始值4
观谦,否則,擴(kuò)容為原來(lái)的兩倍桨菜。
if (capacity > MAX_CACHE_SIZE) { capacity = MAX_CACHE_SIZE; }
MAX_CACHE_SIZE_LOG2 = 16,
MAX_CACHE_SIZE = (1 << MAX_CACHE_SIZE_LOG2),
防止擴(kuò)容后的capacity
超出最大值豁状,最大值為1左移16位捉偏。
之后重新梳理內(nèi)存reallocate(oldCapacity, capacity, true);
,要注意這里傳入的第三個(gè)參數(shù)為true
替蔬,所以會(huì)執(zhí)行reallocate
函數(shù)里的清理舊垃圾的代碼告私。
if (freeOld) {
cache_collect_free(oldBuckets, oldCapacity);
}
cache_collect_free
的定義
static void cache_collect_free(bucket_t *data, mask_t capacity)
{
#if CONFIG_USE_CACHE_LOCK
cacheUpdateLock.assertLocked();
#else
runtimeLock.assertLocked();
#endif
if (PrintCaches) recordDeadCache(capacity);
_garbage_make_room ();
garbage_byte_size += cache_t::bytesForCapacity(capacity);
garbage_refs[garbage_count++] = data;
cache_collect(false);
}
static void _garbage_make_room(void)
{
static int first = 1;
// Create the collection table the first time it is needed
if (first)
{
first = 0;
garbage_refs = (bucket_t**)
malloc(INIT_GARBAGE_COUNT * sizeof(void *));
garbage_max = INIT_GARBAGE_COUNT;
}
// Double the table if it is full
else if (garbage_count == garbage_max)
{
garbage_refs = (bucket_t**)
realloc(garbage_refs, garbage_max * 2 * sizeof(void *));
garbage_max *= 2;
}
}
enum {
INIT_GARBAGE_COUNT = 128
};
作用:上面我們只是給capacity
標(biāo)記擴(kuò)容了,那么內(nèi)存也要相應(yīng)擴(kuò)容承桥。
-
_garbage_make_room
初始時(shí),first=1
根悼,將first = 0
并且開辟128
個(gè)指針類型字節(jié)大小的bucket_t
類型指針凶异,垃圾最大值為128
。這時(shí)候的capacity
為4
挤巡。
如果表已經(jīng)滿了剩彬,也就是垃圾達(dá)到了128
。重新開辟兩倍原來(lái)的garbage_max
大小的內(nèi)存給bucket_t
類型指針矿卑,也就是buckets
喉恋。
否則,則不執(zhí)行任何代碼母廷。 -
garbage_byte_size += cache_t::bytesForCapacity(capacity);
將舊capacity
容量加入整個(gè)垃圾容量里轻黑。 -
garbage_refs[garbage_count++] = data;
將這一次執(zhí)行的方法緩存先置放在garbage_count
下標(biāo)的位置。
總結(jié):前面就是根據(jù)capacity
標(biāo)記進(jìn)行方法緩存空間的申請(qǐng)琴昆,不足則需要擴(kuò)容氓鄙。擴(kuò)容時(shí)會(huì)將之前的緩存抹除掉,重新開辟雙倍大小的空間业舍。擴(kuò)容之后可以直接開始存儲(chǔ)了抖拦,不需要算法掃描是否有同樣的方法緩存,更加快速舷暮。
回到insert
方法态罪。
下面
bucket_t *b = buckets();
mask_t m = capacity - 1;
mask_t begin = cache_hash(sel, m);
mask_t i = begin;
mask_t m = capacity - 1;
根據(jù)這一句,可以知道前面的mask就是capacity - 1
下面,如果初始化的時(shí)候复颈,那么mask=3
,如果擴(kuò)容后诸狭,就是4*2-1=7
券膀。
static inline mask_t cache_hash(SEL sel, mask_t mask)
{
return (mask_t)(uintptr_t)sel & mask;
}
傳入sel
和mask
的算法計(jì)算哈希表存儲(chǔ)的位置。
運(yùn)行斷點(diǎn)調(diào)試驯遇,可以看到當(dāng)執(zhí)行sayMaster
算出的key
值為2
芹彬,再手動(dòng)傳入sayHellow
和sayWorld
的sel
之后,得出的key
分別為0
和1
叉庐。
// 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.
do {
if (fastpath(b[i].sel() == 0)) {
incrementOccupied();
b[i].set<Atomic, Encoded>(sel, imp, cls);
return;
}
if (b[i].sel() == sel) {
// The entry was added to the cache by some other thread
// before we grabbed the cacheUpdateLock.
return;
}
} while (fastpath((i = cache_next(i, m)) != begin));
掃描第一個(gè)未使用的插槽舒帮,保證必定會(huì)有一個(gè)可用插槽,因?yàn)樽钚〕叽缡?code>4,而我們前面已經(jīng)修改了為3/4
就滿了玩郊。
當(dāng)掃描哈希表時(shí)肢执,大多數(shù)情況下b[i].sel() == 0)
成立,也就是空插槽译红,那么就對(duì)occupied
加1
预茄。而
template<Atomicity atomicity, IMPEncoding impEncoding>
void bucket_t::set(SEL newSel, IMP newImp, Class cls)
{
ASSERT(_sel.load(memory_order::memory_order_relaxed) == 0 ||
_sel.load(memory_order::memory_order_relaxed) == newSel);
// objc_msgSend uses sel and imp with no locks.
// It is safe for objc_msgSend to see new imp but NULL sel
// (It will get a cache miss but not dispatch to the wrong place.)
// It is unsafe for objc_msgSend to see old imp and new sel.
// Therefore we write new imp, wait a lot, then write new sel.
uintptr_t newIMP = (impEncoding == Encoded
? encodeImp(newImp, newSel, cls)
: (uintptr_t)newImp);
if (atomicity == Atomic) {
_imp.store(newIMP, memory_order::memory_order_relaxed);
if (_sel.load(memory_order::memory_order_relaxed) != newSel) {
#ifdef __arm__
mega_barrier();
_sel.store(newSel, memory_order::memory_order_relaxed);
#elif __x86_64__ || __i386__
_sel.store(newSel, memory_order::memory_order_release);
#else
#error Don't know how to do bucket_t::set on this architecture.
#endif
}
} else {
_imp.store(newIMP, memory_order::memory_order_relaxed);
_sel.store(newSel, memory_order::memory_order_relaxed);
}
}
這個(gè)函數(shù)將類的sel
和imp
存儲(chǔ)到緩存中。之后return
跳出侦厚。
如果插槽不為空耻陕,則判斷插槽里的sel
和當(dāng)前要存的sel
是否是同一個(gè),如果相同刨沦,說明可能其他線程緩存進(jìn)來(lái)的诗宣,則返回,不存儲(chǔ)想诅。
如果不相同召庞,則執(zhí)行while語(yǔ)句。如果cache表的下一個(gè)插槽的key不等于當(dāng)前begin来破,則執(zhí)行do代碼塊篮灼,如此循環(huán)掃描,直到找到插槽插入讳癌。
static inline mask_t cache_next(mask_t i, mask_t mask) {
return (i+1) & mask;
}
這是cache_next
的算法穿稳。
總結(jié):mask
數(shù)值等于capacity - 1
,使用cache_hash
算法獲得當(dāng)前sel
的在哈希表的begin
開始掃描位置晌坤,如果begin
位置的插槽為空則存儲(chǔ)并返回逢艘,不為空則判斷是否是同一個(gè)sel
,相同則返回骤菠,不同則使用cache_next
算法得到下一個(gè)掃描位置它改,跟當(dāng)前begin
比對(duì),不是同個(gè)插槽商乎,則繼續(xù)循環(huán)上面的查找邏輯央拖。
這樣就可以解釋上面的問題了。
問:_occupied
和_mask
是什么鹉戚,為什么_occupied
都是2
鲜戒,_mask
從2
變成7
,為什么有些方法缺失了抹凳,為什么順序混亂遏餐?
答:_occupied
是占用的方法緩存數(shù),_mask
是緩存容量的capacity-1
赢底,_mask
從2
變成7
是因?yàn)?code>capacity初始值為4
失都,容量擴(kuò)容算法capacity*2
柏蘑,_mask
就等于新的capacity-1
等于7
,而擴(kuò)容會(huì)清理之前的緩存粹庞,導(dǎo)致了方法缺失咳焚,順序混亂是因?yàn)橥ㄟ^哈希算法以sel
為key
存儲(chǔ)導(dǎo)致。
cache_t的原理分析圖如下