iOS-底層(8):類結(jié)構(gòu)之cache_t結(jié)構(gòu)分析

今天我們來研究一下cache_t是什么

前文書我們說過霹疫,在類的結(jié)構(gòu)體中有個(gè)cache_t迫皱,我們來看看在類中的位置

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();
    }
  //...此處省略很多坨代碼
}

cache_t的結(jié)構(gòu)

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;
    
    // How much the mask is shifted by.
    static constexpr uintptr_t maskShift = 48;
    
    // Additional bits after the mask which must be zero. msgSend
    // takes advantage of these additional bits to construct the value
    // `mask << 4` from `_maskAndBuckets` in a single instruction.
    static constexpr uintptr_t maskZeroBits = 4;
    
    // The largest mask value we can store.
    static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
    
    // The mask applied to `_maskAndBuckets` to retrieve the buckets pointer.
    static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << (maskShift - maskZeroBits)) - 1;
    
    // Ensure we have enough bits for the buckets pointer.
    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
    // _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();
    
    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();

    //此處省略

cache_t 的結(jié)構(gòu)分三個(gè)架構(gòu)來處理捐名,macOS i386箩做,模擬器x86近忙,真機(jī)arm64
下面的三個(gè)宏定義:

  • #define CACHE_MASK_STORAGE_OUTLINED 1 運(yùn)行環(huán)境為macOS 或 模擬器
  • #define CACHE_MASK_STORAGE_HIGH_16 2 運(yùn)行環(huán)境為64位真機(jī)
  • #define CACHE_MASK_STORAGE_LOW_4 3 運(yùn)行環(huán)境為非64位真機(jī)等

在真機(jī)環(huán)境下田晚,由于為了進(jìn)一步優(yōu)化內(nèi)存嘱兼,將bucket和mask寫到了一起,使用_maskAndBuckets贤徒,通過掩碼來獲取相應(yīng)數(shù)據(jù)

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

 //....省
public:
    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
    }

    template <Atomicity, IMPEncoding>
    void set(SEL newSel, IMP newImp, Class cls);
};

上面的源碼我們可以得出:IMPSEL就存在bucket_t中芹壕。

一 通過源碼查找 sel和imp

下一個(gè)斷點(diǎn)


image.png
image.png

根據(jù)汇四,地址偏移,指針 和類中提供的方法等一系列操作踢涌,查看打印結(jié)果通孽,當(dāng)前斷點(diǎn)前調(diào)用了兩個(gè)方法,_occupied = 2睁壁,說明方法調(diào)用一次背苦,就會(huì)緩存一次。

二 脫離源碼通過項(xiàng)目查找

在工程中我們定義與cache_t結(jié)構(gòu)類似的類潘明,然后進(jìn)行強(qiáng)轉(zhuǎn)行剂。

#import "LGPerson.h"
#import <objc/runtime.h>

typedef uint32_t mask_t;  // x86_64 & arm64 asm are less efficient with 16-bits

struct lg_bucket_t {
    SEL _sel;
    IMP _imp;
};

struct lg_cache_t {
    struct lg_bucket_t * _buckets;
    mask_t _mask;
    uint16_t _flags;
    uint16_t _occupied;
};

struct lg_class_data_bits_t {
    uintptr_t bits;
};

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
};


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *p  = [LGPerson alloc];
        Class pClass = [LGPerson class];  // objc_clas
        [p say1];
        [p say2];
        [p say3];
        [p say4];
         
        // _occupied  _mask 是什么  cup - 1
        // 會(huì)變化 2-3 -> 2-7
        // bucket 會(huì)有丟失  重新申請
        // 順序有點(diǎn)問題  哈希
  
        // 線索 :
        
        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);
        }

        
        NSLog(@"Hello, World!");
    }
    return 0;
}

  • 我們先只調(diào)用兩個(gè)方法 [p say1] 和[p say2] 我們看打印結(jié)果
image.png
  • 打開say3和say4方法,再次運(yùn)行看打印
image.png

看到上面的打印钳降,我們不禁有些疑問

  • 1厚宰、_mask是什么?
  • 2遂填、_occupied 是什么固阁?
  • 3、為什么隨著方法調(diào)用的增多城菊,其打印的occupiedmask會(huì)變化备燃?
  • 4、bucket數(shù)據(jù)為什么會(huì)有丟失的情況凌唬?并齐,例如2-7中,只有say3客税、say4方法有函數(shù)指針
  • 5况褪、2-7中say3、say4的打印順序?yàn)槭裁词莝ay4先打印更耻,say3后打印测垛,且還是挨著的,即順序有問題秧均?
  • 6食侮、打印的cache_t中的_ocupied為什么是從2開始?

亂序存儲(chǔ)目胡,我們一幫會(huì)想到哈希锯七,那么是否和哈希有關(guān),我們繼續(xù)探索

cache_t底層原理分析

image.png

實(shí)現(xiàn):

void cache_t::incrementOccupied() 
{
    _occupied++;
}

我們看到cache_t中有一個(gè)incrementOccupied方法:增加Occupied
全局搜索一下哪里調(diào)用了

image.png

在cache_t的插入方法里


image.png
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)) {
        // Cache is less than 3/4 full. Use it as-is.
    }
    else {
        capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
        if (capacity > MAX_CACHE_SIZE) {
            capacity = MAX_CACHE_SIZE;
        }
        reallocate(oldCapacity, capacity, true);
    }

    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);
}

首先開辟

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末誉己,一起剝皮案震驚了整個(gè)濱河市眉尸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖噪猾,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件霉祸,死亡現(xiàn)場離奇詭異,居然都是意外死亡袱蜡,警方通過查閱死者的電腦和手機(jī)丝蹭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來戒劫,“玉大人,你說我怎么就攤上這事婆廊⊙赶福” “怎么了?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵淘邻,是天一觀的道長茵典。 經(jīng)常有香客問我,道長宾舅,這世上最難降的妖魔是什么统阿? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮筹我,結(jié)果婚禮上扶平,老公的妹妹穿的比我還像新娘。我一直安慰自己蔬蕊,他們只是感情好结澄,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著岸夯,像睡著了一般麻献。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上猜扮,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天勉吻,我揣著相機(jī)與錄音,去河邊找鬼旅赢。 笑死齿桃,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的煮盼。 我是一名探鬼主播源譬,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼孕似!你這毒婦竟也來了踩娘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎养渴,沒想到半個(gè)月后雷绢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡理卑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年翘紊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片藐唠。...
    茶點(diǎn)故事閱讀 39,773評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡帆疟,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出宇立,到底是詐尸還是另有隱情踪宠,我是刑警寧澤,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布妈嘹,位于F島的核電站柳琢,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏润脸。R本人自食惡果不足惜柬脸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望毙驯。 院中可真熱鬧倒堕,春花似錦、人聲如沸爆价。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽允坚。三九已至魂那,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間稠项,已是汗流浹背涯雅。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留展运,地道東北人活逆。 一個(gè)月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像拗胜,于是被迫代替她去往敵國和親蔗候。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評論 2 354