前言:
在最近學(xué)習(xí)過(guò)程中我們知道一個(gè)類的結(jié)構(gòu)的定義,以及一個(gè)對(duì)象的alloc的執(zhí)行流程。初探底層的源碼。經(jīng)過(guò)最新開(kāi)源的objc781我們知道,類的結(jié)構(gòu)中重要的成員有
-
Class ISA -
Class superclass -
cache_t cache -
class_data_bits_t bits
類的定義代碼如下
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() const {
return bits.data();
}
......... //還包括很多數(shù)據(jù)和方法等
在之前的博客中我們?cè)鴮?duì) isa、class_data_bits_t 已經(jīng)進(jìn)行了一個(gè)自我學(xué)習(xí)和總結(jié)的過(guò)程,接下來(lái)我們就針對(duì)類中很重要的cache_t再次深入進(jìn)行一個(gè)自我學(xué)習(xí)和總結(jié)。希望通過(guò)這樣的學(xué)習(xí)、幫助自己更深刻的理解類的緩存和工作原理。
一、cache_t 的環(huán)境結(jié)構(gòu)
一個(gè)類的結(jié)構(gòu)cache_t大致流程如下:截圖來(lái)自Cooci老師的課件

我們接下來(lái)看看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;
static constexpr uintptr_t maskShift = 48;
static constexpr uintptr_t maskZeroBits = 4;
static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
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.");
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
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;
1在虛擬模擬器中的結(jié)構(gòu)
當(dāng)我們編譯我們代碼中的時(shí)候,相關(guān)的環(huán)境已經(jīng)就確定了;所以我們能看到模擬器和macOS中的結(jié)構(gòu)是
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets;
explicit_atomic<mask_t> _mask;
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
public:
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();
再次進(jìn)入_buckets 能看到 在模擬器和macOS中的結(jié)構(gòu)
explicit_atomic<SEL> _sel;
explicit_atomic<uintptr_t> _imp;
2在真機(jī)調(diào)試中的結(jié)構(gòu)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
public:
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();
再次進(jìn)入_buckets 能看到 在模擬器和macOS中的結(jié)構(gòu)
explicit_atomic<uintptr_t> _imp;
explicit_atomic<SEL> _sel;
這就是cache_t在各個(gè)環(huán)境中的代碼配置結(jié)構(gòu),編譯器會(huì)自動(dòng)根據(jù)環(huán)境進(jìn)入到指定的代碼進(jìn)行編譯和運(yùn)行。非此環(huán)境下的代碼我們想進(jìn)入去查看是進(jìn)不去,這就是編譯器的智能體現(xiàn)。
二、cache_t的SEL本丟查看
我們都知道 對(duì)象調(diào)用方法都是通過(guò)編譯器進(jìn)行方法查找。而編譯器會(huì)經(jīng)常查找的方法進(jìn)行緩存,下次進(jìn)行方法查找的時(shí)候進(jìn)行先進(jìn)入緩存中查找,這樣會(huì)大大節(jié)省時(shí)間,從而達(dá)到快速的作用,cache_t就是為此而生的。正好解決這個(gè)查找問(wèn)題。
接下來(lái)我們分兩種不同的環(huán)境進(jìn)行調(diào)試和學(xué)習(xí)cache_t的內(nèi)部_buckets,也就是sel 和imp,在iOS開(kāi)發(fā)過(guò)程中,
- 1 源碼環(huán)境下指令查看
- 2 脫離源碼進(jìn)行代碼答應(yīng)
1,源碼環(huán)境下指令查看
首先我們創(chuàng)建一個(gè)類LGPerson集成自NSObject 如下
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *lgName;
@property (nonatomic, strong) NSString *nickName;
- (void)sayHello;
- (void)sayCode;
- (void)sayMaster;
- (void)sayNB;
+ (void)sayHappy;
@end
在接下來(lái)進(jìn)行相關(guān)的指令調(diào)試步驟查看相應(yīng)的cache_t
-
1 創(chuàng)建對(duì)象,獲取對(duì)象的類,將斷點(diǎn)斷住相應(yīng)的位置
斷點(diǎn)調(diào)試.png 2 在控制臺(tái)進(jìn)行打印類信息
p/x pClass
結(jié)果是:
(Class) $0 = 0x00000001000022a8 LGPerson
3 進(jìn)行偏移 我們知道
cache_t和類地址相差16位,正好是0x10所以cache_t是0x00000001000022b84 打印
cache_t指針信息;
p (cache_t *)0x00000001000022b8
結(jié)果是
(cache_t *) $1 = 0x00000001000022b8
- 5 取出相關(guān)
cache_t的內(nèi)容;
p *$1
結(jié)果是
(cache_t) $2 = {
_buckets = {
std::__1::atomic<bucket_t *> = 0x000000010032e430 {
_sel = {
std::__1::atomic<objc_selector *> = (null)
}
_imp = {
std::__1::atomic<unsigned long> = 0
}
}
}
_mask = {
std::__1::atomic<unsigned int> = 0
}
_flags = 32804
_occupied = 0
}
- 6 我們知道類的層級(jí)結(jié)構(gòu)后,知道
_buckets里邊存儲(chǔ)的類的sel和imp,從我們打印的結(jié)果知道,此處的_occupied = 0;也就是第一個(gè)斷點(diǎn)的位置還沒(méi)開(kāi)始存儲(chǔ)sel,不信我們繼續(xù);
p $2.buckets()
結(jié)果是:
(bucket_t *) $3 = 0x000000010032e430
- 7 取出
buckets_t中的內(nèi)容
p *$3
結(jié)果是
(bucket_t) $4 = {
_sel = {std::__1::atomic<objc_selector *> = (null}
_imp = {
std::__1::atomic<unsigned long> = 0
}
}
-
8 接下來(lái)我們過(guò)掉一個(gè)斷點(diǎn),執(zhí)行第一個(gè)方法。再次打印結(jié)果;
第二個(gè)斷點(diǎn)調(diào)試.png 9 再次打印
cache_t中的內(nèi)容
p *$1
結(jié)果是
(cache_t) $5 = {
_buckets = {
std::__1::atomic<bucket_t *> = 0x0000000100661c50 {
_sel = {
std::__1::atomic<objc_selector *> = ""
}
_imp = {
std::__1::atomic<unsigned long> = 10584
}
}
}
_mask = {
std::__1::atomic<unsigned int> = 7
}
_flags = 32804
_occupied = 1
}
- 10 我們此時(shí)看到
_occupied = 1也就是緩存中存在了我們調(diào)用的方法了:[p sayHello]已經(jīng)完美執(zhí)行了,接下來(lái)我們?cè)俅悟?yàn)證;
p $5.buckets()
結(jié)構(gòu)是
(bucket_t *) $6 = 0x0000000100661c50
- 11 取出
bucket_t的內(nèi)容;
p *$6
結(jié)果是
(bucket_t) $7 = {
_sel = {
std::__1::atomic<objc_selector *> = ""
}
_imp = {
std::__1::atomic<unsigned long> = 10584
}
}
- 12 取出
sel
p $7.sel()
結(jié)果是
(SEL) $8 = "sayHello"
- 13 取出
imp
p $7.imp(pClass)
結(jié)果是
(IMP) $9 = 0x0000000100000bf0 (KCObjc`-[LGPerson sayHello])
同理過(guò)掉第二個(gè)斷點(diǎn)進(jìn)入第三個(gè),也可以進(jìn)行相關(guān)的打印,_occupied = 2 用相關(guān)的指令也能打印相關(guān)內(nèi)容;
2、脫離源碼進(jìn)行代碼答應(yīng)
從以文章開(kāi)頭介紹,cache_t,依靠系統(tǒng)的幾部分內(nèi)容
- 1
_buckets
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
struct lg_bucket_t {
SEL _sel;
IMP _imp;
};
- 2
cache_t
struct lg_cache_t {
struct lg_bucket_t * _buckets;
mask_t _mask;
uint16_t _flags;
uint16_t _occupied;
};
- 3
class_data_bits_t
struct lg_class_data_bits_t {
uintptr_t bits;
};
- 4
objc_class
struct lg_objc_class {
Class ISA;
Class superclass;
struct lg_cache_t cache; // formerly cache pointer and vtable
struct lg_class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
};
- 5 接下來(lái)創(chuàng)建類,并調(diào)用相關(guān)的兩個(gè)方法
LGPerson *p = [LGPerson alloc];
Class pClass = [LGPerson class]; // objc_clas
[p say1];
[p say2];
// [p say3];
// [p say4];
- 6 配置打印結(jié)果代碼
struct lg_objc_class *lg_pClass = (__bridge struct lg_objc_class *)(pClass);
NSLog(@"%hu - %u",lg_pClass->cache._occupied,lg_pClass->cache._mask);
for (mask_t i = 0; i<lg_pClass->cache._mask; i++) {
// 打印獲取的 bucket
struct lg_bucket_t bucket = lg_pClass->cache._buckets[i];
NSLog(@"%@ - %p",NSStringFromSelector(bucket._sel),bucket._imp);
}
-
7 打印結(jié)果是
兩個(gè)方法的打印結(jié)果.png
我們能看到 _occupied = 2 和 _mask = 3 以及相關(guān)的方法對(duì)應(yīng)的實(shí)現(xiàn) 也就是 sel 和 imp;
-
8 我們把第4步的ISA 注釋掉,打印的結(jié)果卻是
注釋掉類的ISA打印結(jié)果.png
我們能看到 _occupied = 0 和 _mask = 5380272 未知情況
- 9 我們?cè)俅未蛴?個(gè)方法查看打印結(jié)果、
[p say1]、[p say2]、[p say3]、[p say4]
4個(gè)方法答應(yīng)的結(jié)果圖.png
我們能看到 _occupied = 2 和 _mask = 7,明確的知道m(xù)ask 已經(jīng)從原來(lái)的 3 變化到7,那么為什么打印的方法還是只有兩個(gè)呢,這就是我們接下來(lái)研究的mask的機(jī)制和擴(kuò)容的奧秘了。
三、cache_t 的buckets 和mask的機(jī)制探索
從上邊的問(wèn)題 mask 已經(jīng)從原來(lái)的 3 變化到7,就是存在一個(gè)mask 的調(diào)整,那么mask 最大能到多少呢?
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
static constexpr uintptr_t maskShift = 48;
static constexpr uintptr_t maskZeroBits = 4;
static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << (maskShift - maskZeroBits)) - 1;
- maskShift = 48
- maxMask = (1 << 16 ) - 1 = 2^16 -1
-
bucketsMask = (1<<44) - 1 = 2^44 -1
mask 變化前后圖.png
四、cache_t下的sel存儲(chǔ)機(jī)制
我們從objc781 開(kāi)源代碼能清楚的知道cache_t 的過(guò)程是
- 1 cache_fill
- 2 cache_t::insert
- 3 cache_create
- 4 bcopy
- 5 flush_caches
- 6 cache_flush
- 7 cache_collect_free
1 cache_fill
我們知道創(chuàng)建一個(gè)方法需要先走cache_fill,代碼定義如下:
void cache_fill(Class cls, SEL sel, IMP imp, id receiver)
{
runtimeLock.assertLocked();
if (cls->isInitialized()) {
cache_t *cache = getCache(cls);
cache->insert(cls, sel, imp, receiver);
}
}
2 cache_t::insert (最核心)
代碼定義如下
ALWAYS_INLINE
void cache_t::insert(Class cls, SEL sel, IMP imp, id receiver)
{
// part1 計(jì)算相關(guān)的occupied
mask_t newOccupied = occupied() + 1;
unsigned oldCapacity = capacity(), capacity = oldCapacity;
// part2 判斷如果是創(chuàng)建 進(jìn)行初始化
if (slowpath(isConstantEmptyCache())) {
// Cache is read-only. Replace it.
if (!capacity) capacity = INIT_CACHE_SIZE;
reallocate(oldCapacity, capacity, /* freeOld */false);
}
//part3 判斷是否需要擴(kuò)容
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.
}
//part4 擴(kuò)容操作;
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)存 庫(kù)容完畢
}
bucket_t *b = buckets();
mask_t m = capacity - 1;
mask_t begin = cache_hash(sel, m);
mask_t i = begin;
//part5;進(jìn)行相關(guān)的方法存儲(chǔ)
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);
}
首先將代碼的定義分配為5部分;代碼里已經(jīng)注釋的很清楚了
part1 計(jì)算相關(guān)的新的newOccupied
mask_t newOccupied = occupied() + 1;
part2.判讀第一次進(jìn)行初始化操作
- 1 計(jì)算新值
if (!capacity) capacity = INIT_CACHE_SIZE;
reallocate(oldCapacity, capacity, /* freeOld */false);
- 2
INIT_CACHE_SIZE的定義如下
INIT_CACHE_SIZE_LOG2 = 2,
INIT_CACHE_SIZE = (1 << INIT_CACHE_SIZE_LOG2),
也就是1 << 2,就是4.也就是說(shuō)默認(rèn)進(jìn)來(lái)分配4的內(nèi)存空間;
- 3 再進(jìn)行
setBucketsAndMask
setBucketsAndMask(newBuckets, newCapacity - 1);
具體函數(shù)就是
void cache_t::setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask)
{
#ifdef __arm__
mega_barrier();
_buckets.store(newBuckets, memory_order::memory_order_relaxed);
mega_barrier();
_mask.store(newMask, memory_order::memory_order_relaxed);
_occupied = 0;
#elif __x86_64__ || i386
_buckets.store(newBuckets, memory_order::memory_order_release);
_mask.store(newMask, memory_order::memory_order_release);
_occupied = 0;
#else
也即是向內(nèi)存中存儲(chǔ)相關(guān)的sel操作;再次把_occupied = 0 ;也就是不占用任何空間,也就是初始化的的操作,只是一個(gè)空殼子,不存在實(shí)質(zhì)性的操作;
- 4 如果舊的值存在,則全部釋放
cache_collect_free
static void cache_collect_free(bucket_t *data, mask_t capacity)
{
if (PrintCaches) recordDeadCache(capacity);
_garbage_make_room ();
garbage_byte_size += cache_t::bytesForCapacity(capacity);
garbage_refs[garbage_count++] = data;
cache_collect(false);
}
part3 如果新的值小于或等于原來(lái)的3/4,不做任何處理;
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.
}
part4.超過(guò)原來(lái)的3/4,進(jìn)行內(nèi)存擴(kuò)容;
capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE; // 擴(kuò)容兩倍 4
if (capacity > MAX_CACHE_SIZE) {
capacity = MAX_CACHE_SIZE;
}
reallocate(oldCapacity, capacity, true); // 內(nèi)存 庫(kù)容完畢
也就是將原來(lái)的內(nèi)存擴(kuò)容到當(dāng)前的2倍;然后始終保持mask_t m = capacity - 1;這也就是為什么之前我們打印的mask從3變化到7的原因;
因?yàn)槲覀冊(cè)瓉?lái)的內(nèi)存大小是4,因?yàn)橥瑫r(shí)執(zhí)行了4個(gè)方法,存儲(chǔ)已經(jīng)超過(guò)了原來(lái)的3/4,所以擴(kuò)容到
8.而根據(jù)mask_t m = capacity - 1;,所以原來(lái)的是mask = 4- 1 = 3, 而新的mask = 8- 1 = 7
part5方法的存儲(chǔ)機(jī)制
- 1 在iOS開(kāi)發(fā)中我們很多數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)都是以快速為主,例如
字典,內(nèi)存映射等,其目的都是為了快速的查找想要的到的內(nèi)容。同理。cache_t也不例外,其存儲(chǔ)的代碼如下
mask_t begin = cache_hash(sel, m);
mask_t i = begin;
- 2 查看·
cache_hash的內(nèi)部結(jié)構(gòu)
static inline mask_t cache_hash(SEL sel, mask_t mask)
{
return (mask_t)(uintptr_t)sel & mask;
}
3 通過(guò)上面的
cache_hash和mask 進(jìn)行相關(guān)的與操作。我們都知道任何方法在內(nèi)存中都存在一個(gè)方法編號(hào),用這個(gè)方法編號(hào)進(jìn)行與操作,就能準(zhǔn)確的得到這個(gè)方法在cache中的索引;4 如果得到的索引存在沖突,則繼續(xù)處理hash 沖突;
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));
通過(guò)這種方法,那么相關(guān)的方法在類cache_t中就能有并且存儲(chǔ)是一個(gè)唯一的索引,通過(guò)查找方法我們就能快速的查找到;
五、總結(jié)
通過(guò)將近5個(gè)小時(shí)的整理和斷點(diǎn)調(diào)試,終于寫完這次的內(nèi)容,雖然內(nèi)容過(guò)于簡(jiǎn)單,但是還是自己實(shí)現(xiàn)了一遍流程,也算是一種收獲吧,希望以后再接再厲。繼續(xù)努力;如果大神們有什么好的建議請(qǐng)不吝賜教。謝謝。





