ObjC是一門動態性比較強的程式語言,而這一系列的動態性就是依賴於Runtime來支撐的。
isa指標
在iPhone cpu 使用arm64架構之前,isa指標就是一個Class型別的普通指標,儲存著Class、Meta-Class的記憶體地址。在這之後apple對isa指標進行了優化,利用位域的技術,將isa指標換成了一個union(共用體)。arm64架構之後的isa指標每一位都儲存著不同的資訊,在isa的union中有一個結構體表示了每一位對應的含義。
Class
在runtime原始碼中class的定義為:
其中我們可以看到通過bits的data()
函式可以獲取到一個class_rw_t
的結構體指標。class_rw_t
結構體中存放著我們熟悉的東西:方法列表、屬性列表和協議列表等。除此之外還有一個class_ro_t
的結構體指標,它指向的結構體擁有與class_rw_t
結構體類似的結構,也存放有方法列表、協議列表,初次之外還有成員變數列表等資料。
通過class_rw_t
與class_ro_t
的名字我們可以看出,rw代表的是可讀可寫的結構,而ro則代表只讀的結構。
總體上說class的結構可以用下圖表示
在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
有方法名稱(name)、返回值型別和引數型別的編碼(types)、指向函式的指標(imp)。
name
name 就是方法的名稱,它是SEL
型別的。SEL
就是selector是方法選擇器,它是根據方法名字生成的,本質上就是一個字串,跟方法存放的位置無關。也就是說不同類中相同名字的方法的方法選擇器是一樣的。
獲取SEL
的方法可以使用@selector()
或者runtime api中的sel_registerName()
獲得。使用sel_getName()
或者NSStringFromSelector()
將其轉換為字串。
types
types 包含了函式返回值和引數編碼的字串。它就是一個字串,將函式返回值型別和引數型別按照一定的編碼規則生成的一個字串。可以使用@encode()
將具體型別表示成字串編碼。
apple提供的編碼規則如下:
imp
imp在runtime中的定義如下:
正如其註釋所說:這是一個指向方法實現的函式指標。
方法快取
我們知道平時我們進行方法呼叫的時候不可能一直按照從isa指標開查詢方法的規則來查詢方法的實現的。runtime定義的類中有一個方法快取,這裡面快取了呼叫過的方法。當我們呼叫方法的時候runtime會首先從快取中查詢有沒有方法的實現被快取過。如若有,則直接通過快取取出方法進行呼叫,如果沒有則再按照isa指標的流程進行方法實現的查詢。
方法快取是使用雜湊表實現的。SEL作為雜湊表的key,IMP和SEL作為雜湊表的value。
通過上圖我們也可以看出_buckets
是一個陣列,陣列中存放了方法快取的雜湊表。那麼是如何從方法快取中取出方法來的呢?如果是遍歷查詢的話,效率是非常低的,apple利用了一種演算法,它利用@selector(method_name)
和mask(buckets的長度減一)進行一次按位與運算得出一個下標,然後將方法快取存放到這個下標對應的位置,同樣取快取的時候也是進行了這樣一次操作。這樣一來效率就提高了,但是整個陣列中會出現一些沒有用到的空位置,所以這是一種利用空間來換時間的演算法。
END
本篇簡單的介紹了Runtime中關於isa、class、以及方法快取的相關問題。下篇介紹obj方法呼叫的相關問題。