06-將手伸進(jìn)objc_class中的cache, 看看我們調(diào)用的方法是如何緩存的?


  • 我們都知道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)圖

objc_class結(jié)構(gòu)樹-c1342

2: 接下來我們來通過指針偏移試著看看cache里存儲的是什么

cache查找LLDB流程, 圖是月月的!

關(guān)于緩存占用量的計算,有以下幾點說明:

  • buckets() 是個列表, 怎么查找多個呢? 圖是月月的!

    • 利用指針偏移, 數(shù)組的首地址第一個元素的地址

    例: *($4 + 1)

    • 利用列表特性

    例: $3.buckets()[1]

  • 經(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的工作流程

cache 緩存bucket流程圖

疑問解答 --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

cache_next實現(xiàn)-c739

cache緩存bucket流程圖.jpg
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市幕随,隨后出現(xiàn)的幾起案子蚁滋,更是在濱河造成了極大的恐慌,老刑警劉巖赘淮,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辕录,死亡現(xiàn)場離奇詭異,居然都是意外死亡拥知,警方通過查閱死者的電腦和手機(jī)踏拜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來低剔,“玉大人速梗,你說我怎么就攤上這事〗蟪荩” “怎么了姻锁?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長猜欺。 經(jīng)常有香客問我位隶,道長,這世上最難降的妖魔是什么开皿? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任涧黄,我火速辦了婚禮,結(jié)果婚禮上赋荆,老公的妹妹穿的比我還像新娘。我一直安慰自己窄潭,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著月帝,像睡著了一般。 火紅的嫁衣襯著肌膚如雪嚷辅。 梳的紋絲不亂的頭發(fā)上簿姨,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天款熬,我揣著相機(jī)與錄音,去河邊找鬼攘乒。 笑死惋鹅,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的闰集。 我是一名探鬼主播沽讹,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼武鲁,長吁一口氣:“原來是場噩夢啊……” “哼爽雄!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起沐鼠,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤挚瘟,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后饲梭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體乘盖,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年憔涉,在試婚紗的時候發(fā)現(xiàn)自己被綠了订框。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡兜叨,死狀恐怖穿扳,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情国旷,我是刑警寧澤矛物,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站议街,受9級特大地震影響泽谨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一吧雹、第九天 我趴在偏房一處隱蔽的房頂上張望骨杂。 院中可真熱鬧,春花似錦雄卷、人聲如沸搓蚪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽妒潭。三九已至,卻和暖如春揣钦,著一層夾襖步出監(jiān)牢的瞬間雳灾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工冯凹, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留谎亩,地道東北人。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓宇姚,卻偏偏與公主長得像匈庭,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子浑劳,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,955評論 2 355