我再來分享一個底層知識點另伍,學(xué)到了之后不寫出來總覺得不是自己的,關(guān)于cache的數(shù)據(jù)結(jié)構(gòu)愁茁,首先cache是什么呢?
這個英文單詞就是緩存的意思亭病,那他緩存的是什么呢鹅很?我們大多數(shù)人肯定都不太了解,我之前獲取bits罪帖,可以進行內(nèi)存平移促煮,那我獲取cache是不是也可以進行內(nèi)存平移呢邮屁?
一試便知,我在objc的源碼工程里面寫了一個demo菠齿,自定義了一個類 LGPerson 繼承自 NSObject佑吝,來到main里面,通過 class 拿到這個類绳匀,然后在下一行打一個斷點芋忿,如下圖。
運行疾棵,停在斷點處之后戈钢,我來進行萬能的LLDB調(diào)試。
拿到 pClass 的地址之后給他打印出來是尔,但是直接打印是不行的殉了,還要進行一下強轉(zhuǎn)。
強轉(zhuǎn)之前拟枚,我先給他平移16個字節(jié)薪铜,就是加10,變成了0x0000000100008460恩溅,然后強轉(zhuǎn)的話隔箍,轉(zhuǎn)成什么類型呢?
就是 cache 的類型暴匠,那 cache 是什么類型我不記得了鞍恢,我就去源碼里面找一下,全局搜索objc_class每窖。
可以在后面加一個空格帮掉,結(jié)果會少很多,找到 new.h結(jié)尾的文件窒典,然后里面就有這個 objc_class 結(jié)構(gòu)體蟆炊,點進去。
這里就找到了cache瀑志,這個過程需要對底層原理有比較好的掌握涩搓,cache 是什么類型就一目了然了,是一個cache_t 類型劈猪,我拷貝出來昧甘,加個星號表示還原他的指針。
回車就得到了 cache_t 類型的 $1战得,然后我來看里面的數(shù)據(jù)充边。
打印得到了這么一長串內(nèi)容,主要有這么幾個變量名:_bucketsAndMaybeMask、_maybeMask浇冰、_flags贬媒、_occupied、_originalPreoptCache肘习。
那這些東西我看不懂际乘,不知所云,所以我需要在源碼里面找到對應(yīng)的說辭漂佩,怎么找呢脖含,我 command 點擊 cache_t,Jump to Definition投蝉。
很明顯器赞,cache_t 也是一個結(jié)構(gòu)體,然后有一個private墓拜,里面就有這個 _bucketsAndMaybeMask 等一些變量。
跟我打印出來的一模一樣请契,除了 _bucketsAndMaybeMask之外咳榜,其他幾個都被 union 一個大括號包進去了,他們是一個聯(lián)合體爽锥。
很多人可能對于這種結(jié)構(gòu)不那么了解涌韩,我再來補充一個點,在真機上的app氯夷,是運行在 arm64 架構(gòu)上的臣樱,模擬器呢?i386腮考,Mac呢雇毫?
x86_64,M1 的 Mac 也是 arm64踩蔚,這是跟硬件有關(guān)的棚放,那這個聯(lián)合體里面有一個 #if LP64 是什么呢?LP64 就是適用在 Linux 和 macOS 電腦上面的一個數(shù)據(jù)模型馅闽,我找了個表格給大家看一下飘蚯。
就很明顯了,LP64 就是指代 Unix 系列的操作系統(tǒng)福也,所以都是跑的64位局骤,也就是說,這個 if 條件是成立的暴凑。
整個 cache_t 的數(shù)據(jù)結(jié)構(gòu)就能夠很清晰很直觀的了解了峦甩,那么問題來了,cache是緩存搬设,那到底緩存什么呢穴店?要么緩存屬性撕捍,要么緩存方法,這里我看到了屬性泣洞,但是沒看到方法忧风。
按理說也應(yīng)該緩存方法才對,如果是緩存方法的話球凰,我應(yīng)該在這個結(jié)構(gòu)體里面能看到 sel 和 imp狮腿,但是并沒有,是不是意味著他不是緩存這些東西呢呕诉?我先賣個關(guān)子缘厢。
接下來又有一個問題,這幾個屬性里面甩挫,哪一個才是我們需要關(guān)注的贴硫、最重要的那個?打印信息里面兩個沒有值的可以排除伊者,剩下的我也不知道哪個最重要英遭,那怎么辦呢?
這里又有一個閱讀源碼的小技巧亦渗,就是看他提供的功能方法挖诸,這個cache_t,是一個緩存法精,也就必定是一個存儲的過程多律,意味著他一定會有東西進行了讀寫,或者增刪改查等等的操作搂蜓。
所以狼荞,我再繼續(xù)往下找方法,在481行就找到了一個 insert洛勉,也就是插入粘秆,正好命中了我剛剛的猜想,他離不開增刪改查收毫,并且他的參數(shù)正好就有 sel 和 imp攻走。
所以,我來看看他里面有沒有我想要的東西此再,點進去昔搂,看到了兩個 sel(),都是由 bucket_t 對象中的元素進行調(diào)用输拇,也就是對 bucktet_t 進行了一些操作摘符,難道關(guān)鍵就在bucket_t ?(下圖源碼進行了一些刪減)
正好在之前的 cache_t 結(jié)構(gòu)體中找 insert 的時候也看到了不少 bucket_t。
感覺他的重心應(yīng)該就是這個bucket_t逛裤,我直接點進去瘩绒,看到如下圖的內(nèi)容。
這就恍然大明白了带族,就是千呼萬喚的 imp 和 sel锁荔,bucket 單詞是“桶”的意思,就是一個容器蝙砌,裝了很多的imp 和 sel阳堕,并且一個 imp 就對應(yīng)了一個 sel,梳理一下择克。
cache 里面有一個 buckets恬总,buckets 里面就會有一個 sel 和 imp,但是這個具體的結(jié)構(gòu)肚邢,我還不是非常的清楚壹堰,但是知道這么多就已經(jīng)足夠了。
然后我要去驗證里面的值骡湖,是不是真的有缀旁,是不是真的是這樣的呢?眼見不一定為實勺鸦,自己操作一遍才放心,那我繼續(xù)LLDB調(diào)試目木。
之前已經(jīng)拿到了$2函匕,然后他的重點是buckets鸭轮,所以我是不是應(yīng)該打印這個_bucketsAndMaybeMask 呢?只有這個里面有 buckets 這個詞,試一試饰躲。
然后我去拿他的Value,因為也沒其他東西可以拿唇牧。
竟然拿不到风范!又進了死胡同,LLDB調(diào)試不出來了摹恰,怎么辦辫继?這個時候又回到了上面提到的調(diào)試技巧,我只能去找他有沒有合適的方法俗慈。
這是LLDB調(diào)試遇到問題的時候最常見的辦法姑宽,那我去 cache_t 結(jié)構(gòu)體中找一下是不是有g(shù)et相關(guān)的方法,別說闺阱,還真有炮车。
這里得到了一個結(jié)構(gòu)體指針 buckets,感覺事情變得簡單了,我拷貝 buckets() 繼續(xù)進行調(diào)試瘦穆。
果真拿到了一個 bucket_t 的地址纪隙,那這個地址里面是什么我也不知道,打印出來看一下扛或。
跟之前的源碼分析一模一樣绵咱,就是 imp 和 sel,但是他們什么都沒有告喊,很尷尬麸拄,不過也沒關(guān)系,我這里已經(jīng)可以得到一些信息了黔姜,bucket_t 結(jié)構(gòu)體里面是有一個 #if 判斷的拢切。
這個條件語句里面,我是走的 if 還是 else 呢秆吵,我都不用分析這個條件淮椰,對比一下打印出來的 $4 就知道了,先 imp纳寂,然后 sel主穗,所以走的是 arm64,大多數(shù)人在這里應(yīng)該都是走的 else毙芜,為什么呢忽媒?
因為我是M1的Mac,我已經(jīng)走在了時尚的最前沿腋粥,哈哈哈晦雨,大多數(shù)人都是 x86,所以走的是 else隘冲,如果是真機環(huán)境的話闹瞧,走的也是 arm64,其實區(qū)別不大展辞,了解一下就好了奥邮。
但是他們?yōu)槭裁炊紱]有呢?因為沒有調(diào)用方法罗珍!沒有調(diào)用方法洽腺,他有個LLDB的緩存啊,那我再來調(diào)用一下方法覆旱,這個 LGPerson 我已經(jīng)寫好了一個實例方法 saySomething已脓。
調(diào)完方法之后,上面的流程我再來一次通殃。
imp 的 Value 不出所料有值了度液!但是你們操作的時候厕宗,可能還是會沒有東西,如果你們沒有的話堕担,怎么辦呢已慢?
我也提一嘴,這是一個 buckets霹购,他有個s字母結(jié)尾佑惠,就意味著他是一個數(shù)組,所以你可以在后面加上一個[1]齐疙,取他下一個bucket膜楷,或許你就有值了。
如果沒有多個就可以直接取贞奋,這里涉及到了哈希函數(shù)赌厅,因為哈希函數(shù)的下標是不一定的,普通的數(shù)組是從零開始的轿塔,但是哈希就不同特愿,而且他還是無序的。
但是這個 $10 還不是我想看到的結(jié)果勾缭,我想看的是最終打印出 saySomething揍障,才能證明我們的源碼分析沒有問題,那我還是同樣的來看 bucket_t 結(jié)構(gòu)體里面有沒有相應(yīng)的方法俩由。
找到一個 sel()毒嫡,不解釋了,直接拿過來用幻梯。
打印出了 saySomething审胚,就這?簡單得很嘛礼旅,所以,我也應(yīng)該同樣的可以改成 imp()洽洁。
竟然報錯了痘系,問題不大,這個錯誤提示很熟悉饿自,翻譯過來就是參數(shù)太少了汰翠,預(yù)想的是有2個參數(shù),但是我沒有給昭雌,只有0個复唤,如果你也這么操作了,說明你肯定沒有去仔細看源碼烛卧,imp() 就在 sel() 下面一點點佛纫。
這里他需要兩個參數(shù)妓局,一個 bucket_t,一個 class呈宇,這個 class 我可以給你好爬,很簡單,就是 pClass甥啄,那這個bucket_t 我怎么給存炮?我不知道,我也不想知道蜈漓,感覺很麻煩的亞子穆桂,那我直接丟個 nil,回車融虽。
漂亮享完,來自 KCObjcBuild 里面的 LGPerson 的實例方法 saySomething,完美打印出來衣形,到這里驼侠,cache的數(shù)據(jù)結(jié)構(gòu)分析,就基本搞定谆吴,到此結(jié)束倒源。
最后呢,如果對于文章內(nèi)容有任何疑問都可以進行留言句狼,objc源碼我就不放了笋熬,網(wǎng)上一大把,如果某些內(nèi)容有誤也懇請幫我指出來腻菇,共同進步胳螟。