iOS得層探索 --- 類的結(jié)構(gòu)探索(下)

image

iOS底層探索 --- 類的結(jié)構(gòu)探索(上)中我們分析了cache_t的大小盯质。今天我們來探索一下cache_t里面到底存放了些什么宰僧。


1第美、cache_t源碼查看

1.1 源碼簡單分析

首先我們要從源碼中尋找,看看cache_t到底長什么樣子将谊。

在這里首先要跟打下確認(rèn)幾點(diǎn)內(nèi)容:

  • CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS:表示運(yùn)行的環(huán)境是MacOS见转,或者是模擬器管削。。
  • CACHE_MASK_STORAGE_HIGH_16:表示運(yùn)行的環(huán)境是64位的真機(jī),一般是指ARM64架構(gòu)的里伯。
  • CACHE_MASK_STORAGE_LOW_4:表示非64位的真機(jī)城瞎,一般指32位的。
  • CACHE_MASK_STORAGE_OUTLINED:表示未識別的設(shè)備疾瓮。
image

我們在閱讀cache_t源碼的時候,里面有很多內(nèi)容,一時間也看不出來到底有什么用匈织。同樣的盆均,探索的過程終究是比較枯燥的。在漫長的探索過程中肩碟,發(fā)現(xiàn)了這個:bucket_t

image

為什么是bucket_t呢强窖?因為我在bucket_t的定義中發(fā)現(xiàn)了我想要的東西:

image

正常的緩存,一定要存儲方法的腾务。既然在bucket_t里面找到了impsel毕骡;那么說明這條思路是對的,我們順著這條思路繼續(xù)探索岩瘦。


1.2 LLDB打印緩存方法

既然我們大致濾清了cache_t中方法的存儲形式未巫,那么我們就通過控制臺去打印一下。

我們沿用之前的代碼:


image

我們的初次LLDB運(yùn)行到下面階段的時候启昧,遇到了問題叙凡。究竟cache_t里的緩存方法存在哪里呢?(注意:這里指針平移16字節(jié)

image

上圖中$3的結(jié)構(gòu)密末,對應(yīng)的就是源碼中的數(shù)據(jù)結(jié)構(gòu):

image

這里我猜測應(yīng)該是_originalPreoptCache握爷,存儲著緩存方法。但是在繼續(xù)探索的時候严里,發(fā)現(xiàn)并沒有緩存方法新啼。過程如下:

image

此時應(yīng)該換一個思路,看一看cache_t中有沒有一些對應(yīng)的方法刹碾,于是發(fā)現(xiàn)了buckets()

image

這個時候燥撞,我們執(zhí)行以下buckets()

image

到這里我們終于找到了selimp。但是會發(fā)現(xiàn)迷帜,里面并沒有數(shù)據(jù)物舒,這是因為我們并沒有調(diào)用方法,所以沒有緩存數(shù)據(jù)戏锹。

既然沒有緩存數(shù)據(jù)冠胯,那么我們就執(zhí)行以下方法func,創(chuàng)造緩存數(shù)據(jù)锦针。但是當(dāng)我們執(zhí)行了方法func之后荠察,發(fā)現(xiàn)還是沒有數(shù)據(jù)置蜀,不過maybeMask產(chǎn)生了變化:

image

這里主要是因為緩存方法的存儲是根據(jù)哈希值來計算下標(biāo)的。我這邊從新執(zhí)行了割粮,然后得到了需要的數(shù)據(jù)盾碗。(哈希值的內(nèi)容,我們文章結(jié)尾再探討)

image

此時我們可以通過sel()imp(UNUSED_WITHOUT_PTRAUTH bucket_t *base, Class cls)這兩函數(shù)來獲得具體的selimp

image
  • sel:

    image

  • imp:

    image


2 非源碼查看緩存

正常情況下舀瓢,我們從官網(wǎng)獲取的源碼是不能夠編譯的廷雅。有些情況下,我們?nèi)ヅ渲迷创a的時候京髓,也不一定能夠成功讓其編譯通過航缀。(我這邊使用的是命令行工程)

這個時候我們可以采取另外一種方式,讓我們可以繼續(xù)進(jìn)行源碼的探索堰怨。那就是\color{red}{將源碼芥玉,部分拷貝到我們自己的項目中(注意,不是全部拷貝)}备图,舉個例子如下:

  • 拷貝obj_class
    舉個例子灿巧,我們在探索源碼的時候,都要經(jīng)過obj_class揽涮,所以我們將obj_class的部分代碼拷貝出來抠藕,修改成我們自己的名字,拷貝的內(nèi)容也是一些屬性等關(guān)鍵信息蒋困。
struct jax_objc_class {
    Class isa;
    Class superclass;
    struct jax_cache_t cache;
    struct jax_class_data_bit_t bits;
};
  • 整個拷貝之后的代碼如下:
typedef uint32_t mask_t;  // x86_64 & arm64 asm are less efficient with 16-bits

struct jax_bucket_t {
    SEL _sel;
    IMP _imp;
};

struct jax_cache_t {
    struct jax_bucket_t *_bukets;  // 8
    mask_t _maybeMask;             // 4
    uint16_t _flags;               // 2
    uint16_t _occupied;            // 2
};

struct jax_class_data_bit_t {
    uintptr_t bits;
};

struct jax_objc_class {
    Class isa;
    Class superclass;
    struct jax_cache_t cache;
    struct jax_class_data_bit_t bits;
};
  • 創(chuàng)建Person類盾似,并實現(xiàn)一些測試方法:
image
  • 接下來我們在main函數(shù)里面檢測一下我們拷貝出來的代碼是否可用。這里我們隨便打印一下cache里面的信息:
image
  • 由于我們有很多的方法雪标,所以我們可以循環(huán)打印一下


    image
  • 增加方法調(diào)用零院,再次循環(huán)打印村刨;但是當(dāng)我們再次循環(huán)打印的時候告抄,發(fā)現(xiàn)輸出的打印信息不正常:

image

3 cache_t 底層原理探索

在上面我們調(diào)用多個對象方法的時候,我們的循環(huán)打印發(fā)生了異常嵌牺。
并且還發(fā)現(xiàn)_occupied_maybeMask也發(fā)生了變化打洼。

這究竟是為什么呢?我們還是需要從源碼中尋找答案髓梅。

3.1 occupied

首先關(guān)于occupied的變化拟蜻,我們發(fā)現(xiàn)了這個函數(shù):void incrementOccupied();

image

image

也就是說incrementOccupied()會讓_occupied進(jìn)行自加操作绎签。
那么我們就要知道它在哪里別調(diào)用枯饿。

通過搜索發(fā)現(xiàn),它在cache_tinsert方法里面被調(diào)用:

image

3.2 insert

其實在看到insert方法的時候诡必,我們就應(yīng)該有所感覺了奢方。對應(yīng)緩存搔扁,肯定是要有插入方法的。cache_tinsert正是其插入方法蟋字。

image

接下來我們分析以下insert源碼:

image

上面這部分內(nèi)容稿蹲,描述了緩存空間的開辟,其中有一個方法reallocate值得我們?nèi)パ芯恳幌隆?/p>

因為鹊奖,初始化擴(kuò)容的時候苛聘,都用到了這個方法,但是忠聚,傳入的參數(shù)卻不相同设哗。

  • reallocate
    image

可以看到,開啟緩存空間的方法很簡單两蟀,首先是根據(jù)傳入的值開辟新的緩存空間网梢;然后判斷是否有舊的緩存,如果有就釋放舊的緩存赂毯。

既然緩存空間已經(jīng)開辟完畢了战虏,那接下來就應(yīng)該是selimp相關(guān)的操作了。

image
  • cache_hask

這個是計算哈希值的函數(shù):

// Class points to cache. SEL is key. Cache buckets store SEL+IMP.
// Caches are never built in the dyld shared cache.

static inline mask_t cache_hash(SEL sel, mask_t mask) 
{
    uintptr_t value = (uintptr_t)sel;
#if CONFIG_USE_PREOPT_CACHES
    value ^= value >> 7;
#endif
    return (mask_t)(value & mask);
}
  • cache_nest

這個是計算哈希沖突的函數(shù):

#if CACHE_END_MARKER
static inline mask_t cache_next(mask_t i, mask_t mask) {
    return (i+1) & mask;
}
#elif __arm64__
static inline mask_t cache_next(mask_t i, mask_t mask) {
    return i ? i-1 : mask;
}
#else
#error unexpected configuration
#endif

3.3 上面問題解答

我們在上面党涕,調(diào)用多個對象方法的時候烦感,循環(huán)打印出錯了。接著我們探究了源碼中的insert方法∏补模現(xiàn)在我們可以對這個現(xiàn)象做出解釋了啸盏。

  • 對象方法調(diào)用的增加,_occupied_maybeMask都變化了
    這是因為在cache初始化的時候骑祟,分配的空間是4個(INIT_CACHE_SIZE == 4)回懦;隨著方法調(diào)用的增加,緩存空間不夠用了次企,根據(jù)源碼中的擴(kuò)容算法怯晕,對緩存空間進(jìn)行了兩倍擴(kuò)容。

  • mask
    在哈希相關(guān)的函數(shù)中缸棵,我們看到了這個參數(shù)舟茶;這是掩碼mask = capacity -1capacity`是容量的意思堵第。

  • _occupied
    字面意思理解是占據(jù)吧凉,占位的意思,可以理解為緩存中已經(jīng)存在的sel-imp的個數(shù)踏志。
    導(dǎo)致_occupied變化的因素有以下幾個:

    • init
    • 屬性賦值
    • 方法調(diào)用
  • 上面的循環(huán)打印阀捅,出現(xiàn)空值是怎么回事?
    這個是緩存空間重新分配造成的针余,舊的空間被釋放饲鄙,新的空間`重新分配凄诞。

  • sel-imp在緩存中的存儲順序
    這一點(diǎn)大家要注意,由于下標(biāo)是通過哈希計算出來的忍级,所以順序是不固定的帆谍,沒有先后之分。這一點(diǎn)大家可以參考cache_t::insert函數(shù)的后半部分轴咱。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末汛蝙,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子朴肺,更是在濱河造成了極大的恐慌患雇,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宇挫,死亡現(xiàn)場離奇詭異苛吱,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)器瘪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門翠储,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人橡疼,你說我怎么就攤上這事援所。” “怎么了欣除?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵住拭,是天一觀的道長。 經(jīng)常有香客問我历帚,道長滔岳,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任挽牢,我火速辦了婚禮谱煤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘禽拔。我一直安慰自己刘离,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布睹栖。 她就那樣靜靜地躺著硫惕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪野来。 梳的紋絲不亂的頭發(fā)上恼除,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機(jī)與錄音梁只,去河邊找鬼缚柳。 笑死,一個胖子當(dāng)著我的面吹牛搪锣,可吹牛的內(nèi)容都是我干的秋忙。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼构舟,長吁一口氣:“原來是場噩夢啊……” “哼灰追!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起狗超,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤弹澎,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后努咐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體苦蒿,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年渗稍,在試婚紗的時候發(fā)現(xiàn)自己被綠了佩迟。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡竿屹,死狀恐怖报强,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情拱燃,我是刑警寧澤秉溉,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站碗誉,受9級特大地震影響召嘶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜哮缺,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一苍蔬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蝴蜓,春花似錦碟绑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至诵冒,卻和暖如春凯肋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背汽馋。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工侮东, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留圈盔,地道東北人。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓悄雅,卻偏偏與公主長得像驱敲,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子宽闲,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容