我們都知道,在Objective-c里面,調用一個方法,其實在runtime層的時候會翻譯成
objc_msgSend(receiver, SEL)
可以想象一下,在繼承關系中,一個比較深度的子類去調用父類的父類的父類... ...的方法的時候,如果沒有緩存,每次都會用isa指針
去挨個搜索,查找鏈是非常長的,如果類中的方法比較多,比較費時費力,可以看一個比較明顯的例子:
在這種情況下,如果沒有方法緩存,查找會變得非常耗時。
首先,先看看,方法緩存是放在哪個地方的,在類的定義中就有方法緩存,具體代碼如下:
所以,是方法緩存是根據
類
來的, 并不是根據具體的類的對象
來的。
方法緩存的實現可以到runtime源碼中去看,為了優化性能,objc_msgSend
是用匯編來實現的,在objc-msg-arm.s文件中,具體實現的步驟是:
判斷receiver是否是nil。
從緩存里面尋找SEL,找到就分發,否則3。
跳轉到
_objc_msgSend_uncached
,利用_class_lookupMethodAndLoadCache3
方法(objc-class.mm中,具體可以看下面)尋找SEL。
對應的代碼為:
從代碼中可以看到, 如果沒有找到方法緩存,就會跳轉到\_objc\_msg\_uncached
這里,里面有\_class\_lookupMethodAndLoadCache3
這個函數的具體的實現如下:
根據注釋可以知道:此方法可以避免再去緩存查找方法,直接去方法列表去找。
其中:
mask
: 表示當前緩存能達到的最大的size,從0開始,所以total = mask + 1occupied
: 表示占用的內存標志,順便說一句,方法緩存是通過 “散列表” 的形式 實現的,散列表根據 哈希算法來定位位置,所以會產生空位,occupied用來表示已經使用的內存的個數-
buckets
:就是用數組來表示存儲緩存的散列表的存儲空間,其中的每一個Method類型表示一個可用的方法緩存。注意:其中結構體中,最后一個成員用[1], 說明中這是一個“可變數組”,在我以前接觸到的c語言中,發現有的平臺是用[0]來表示,有的是用[1]來表示可變數組
具體到Method的定義:
name
: 表示被緩存的方法名字types
: 存儲著方法的的參數類型和返回值類型imp
: 就是方法的具體實現
還有,往散列表中 存方法緩存
和 取方法緩存
- 存方法緩存是在objc-cache-old.mm文件中實現的,
12.png
這里就是往散列表中存儲的具體實現,其中的散列查找算法是:
位置是通過sel指針偏移后和mask與后的結果得出
- 從緩存中取方法
取緩存的代碼是 跟objc_msgSend的實現在同一個文件中,obj-msg-arm.s中,為了查找的性能優化,也是通過匯編來實現的,方法名字是 CacheLookup,具體實現是:
根據查閱其中的 匯編關鍵字 以及注釋,可以知道,取緩存和加緩存的邏輯差不多,也是根據hash去定位,如果出現沖突,根據解決hash沖突規則,繼續hash, 直到找到為止,這里是 ++ 的實現形式