OC對(duì)象的方法緩存

一、回顧

在程序運(yùn)行的時(shí)候,oc對(duì)象在內(nèi)存中的存儲(chǔ)結(jié)構(gòu)是objc_class類型的,objc_class存放著類的方法列表,屬性列表,協(xié)議列表,成員變量列表
還存放著方法的緩存列表

二、方法緩存列表

思考:為什么要?jiǎng)?chuàng)建方法緩存列表,目的是什么

1.方法的調(diào)用

假如調(diào)用對(duì)象方法的時(shí)候,首先從對(duì)象的方法列表中查詢方法,如果不存在,通過superclass查找父類的方法列表,直到找到方法。


image.png

按照這樣的邏輯執(zhí)行的話,每次調(diào)用方法都會(huì)去查詢方法,這樣會(huì)造成資源的浪費(fèi)

加入存在一個(gè)方法的緩存列表,第一次調(diào)用方法的時(shí)候,都將這個(gè)方法緩存起來,那么下次再調(diào)用這個(gè)方法的時(shí)候,就可以直接從緩存列表中讀取,不需要再按照流程去查詢方法


image.png

再次調(diào)用使用過的方法的時(shí)候,直接從緩存列表中讀取

三、緩存列表的內(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);
};

四、存儲(chǔ)與查詢方式

/// 查詢方法的方式
//通過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è)位置查詢,直到返回結(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);
}

通過源碼分析發(fā)現(xiàn),蘋果不是單純的通過遍歷的方式查詢方法的位置。 而是通過將 sel 與 mask進(jìn)行 &預(yù)算計(jì)算出 方法存放的下標(biāo),如果下標(biāo)內(nèi)已經(jīng)存放了方法,會(huì)繼續(xù)往上一個(gè)位置存放

如果散列表中分配的內(nèi)存已經(jīng)存滿,會(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);
}

如果緩存列表滿了,會(huì)清空緩存列表,擴(kuò)容內(nèi)存為原來的兩倍,等待下次調(diào)用方法的時(shí)候重新進(jìn)行緩存,因?yàn)?_mask 存放的數(shù)量 已經(jīng)改變了,進(jìn)行 &運(yùn)算的時(shí)候,肯定是和之前的值不一樣,所以需要清空,重新緩存

五、總結(jié)

方法緩存的存儲(chǔ)方式 為散列表存儲(chǔ)
1.通過方法名 & 當(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.如果全部都滿了,就會(huì)重新分配內(nèi)存,重新進(jìn)行緩存


image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容