ObjC Runtime簡析

SoC發表於2019-02-03

ObjC是一門動態性比較強的程式語言,而這一系列的動態性就是依賴於Runtime來支撐的。

isa指標

在iPhone cpu 使用arm64架構之前,isa指標就是一個Class型別的普通指標,儲存著Class、Meta-Class的記憶體地址。在這之後apple對isa指標進行了優化,利用位域的技術,將isa指標換成了一個union(共用體)。arm64架構之後的isa指標每一位都儲存著不同的資訊,在isa的union中有一個結構體表示了每一位對應的含義。

isa指標

Class

在runtime原始碼中class的定義為:

ObjC Runtime簡析

其中我們可以看到通過bits的data()函式可以獲取到一個class_rw_t的結構體指標。class_rw_t結構體中存放著我們熟悉的東西:方法列表、屬性列表和協議列表等。除此之外還有一個class_ro_t的結構體指標,它指向的結構體擁有與class_rw_t結構體類似的結構,也存放有方法列表、協議列表,初次之外還有成員變數列表等資料。

通過class_rw_tclass_ro_t的名字我們可以看出,rw代表的是可讀可寫的結構,而ro則代表只讀的結構。

總體上說class的結構可以用下圖表示

class_ro_t

class_rw_t中方法列表、屬性列表、協議列表都是xxx_array_t二維陣列,而class_ro_t中的方法列表,協議列表等都是xxx_list_t的一維陣列。class_rw_t中的二維陣列中的存放的就是許多xxx_list_t的一維陣列。這些一維陣列可能是來自原始類(class_ro_t中存放的是類的原始的資訊),也可能是來自category。

method_t

對於methodlist而言,一個方法就是method_list_t的一個元素,其關係可以用下圖表示:

method_t存放的位置

也就是說method_t才方法列表中儲存的最小元素。method_t的是一個結構,它是對方法/函式的封裝。

method_t

method_t有方法名稱(name)、返回值型別和引數型別的編碼(types)、指向函式的指標(imp)。

name

name 就是方法的名稱,它是SEL型別的。SEL就是selector是方法選擇器,它是根據方法名字生成的,本質上就是一個字串,跟方法存放的位置無關。也就是說不同類中相同名字的方法的方法選擇器是一樣的。

獲取SEL的方法可以使用@selector()或者runtime api中的sel_registerName()獲得。使用sel_getName()或者NSStringFromSelector()將其轉換為字串。

types

types 包含了函式返回值和引數編碼的字串。它就是一個字串,將函式返回值型別和引數型別按照一定的編碼規則生成的一個字串。可以使用@encode()將具體型別表示成字串編碼。

apple提供的編碼規則如下:

types的編碼規則

imp

imp在runtime中的定義如下:

ObjC Runtime簡析

正如其註釋所說:這是一個指向方法實現的函式指標。

方法快取

我們知道平時我們進行方法呼叫的時候不可能一直按照從isa指標開查詢方法的規則來查詢方法的實現的。runtime定義的類中有一個方法快取,這裡面快取了呼叫過的方法。當我們呼叫方法的時候runtime會首先從快取中查詢有沒有方法的實現被快取過。如若有,則直接通過快取取出方法進行呼叫,如果沒有則再按照isa指標的流程進行方法實現的查詢。

方法快取

方法快取是使用雜湊表實現的。SEL作為雜湊表的key,IMP和SEL作為雜湊表的value。

通過上圖我們也可以看出_buckets是一個陣列,陣列中存放了方法快取的雜湊表。那麼是如何從方法快取中取出方法來的呢?如果是遍歷查詢的話,效率是非常低的,apple利用了一種演算法,它利用@selector(method_name)和mask(buckets的長度減一)進行一次按位與運算得出一個下標,然後將方法快取存放到這個下標對應的位置,同樣取快取的時候也是進行了這樣一次操作。這樣一來效率就提高了,但是整個陣列中會出現一些沒有用到的空位置,所以這是一種利用空間來換時間的演算法。

查詢快取

END

本篇簡單的介紹了Runtime中關於isa、class、以及方法快取的相關問題。下篇介紹obj方法呼叫的相關問題。

相關文章