Class的結構
class_rw_t
class_rw_t里面的methods弟灼、properties、protocols是二維數(shù)組,是可讀可寫的惫企,包含了類的初始內容、分類的內容
class_ro_t
class_ro_t里面的baseMethodList陵叽、baseProtocols雅任、ivars、baseProperties是一維數(shù)組咨跌,是只讀的,包含了類的初始內容
底層運行邏輯:編寫代碼運行后,開始類的方法,成員變量 屬性 協(xié)議等信息都存放在 const class_ro_t中,運行過程中,會將信息整合,動態(tài)創(chuàng)建 class_rw_t,然后會將 class_ro_t中的內容(類的原始信息:方法 屬性 成員變量 協(xié)議信息) 和 分類的方法 屬性 協(xié)議 成員變量的信息 存儲到 class_rw_t中.并通過數(shù)組進行排序,分類方法放在數(shù)組的前端,原類信息放在數(shù)組的后端.運行初始 objc-class中的 bits是指向 class_ro_t的,bits中的data取值是從class_ro_t中獲得,而后創(chuàng)建 class_rw_t,class_rw_t中的 class_ro_t從初始的 class_ro_t中取值,class_rw_t初始化完成后,修改 objc_class中的bits指針指向class_rw_t
method_t
-
method_t是對方法\函數(shù)的封裝
- IMP代表函數(shù)的具體實現(xiàn)
typedef id _Nullable (*IMP)(id _Nonnull,SEL _Nonnull,...);
- SEL代表方法\函數(shù)名硼婿,一般叫做選擇器锌半,底層結構跟char *類似
- 可以通過@selector()和sel_registerName()獲得
- 可以通過sel_getName()和NSStringFromSelector()轉成字符串
- 不同類中相同名字的方法,所對應的方法選擇器是相同的
typedef struct objc_selector *SEL;
types包含了函數(shù)返回值寇漫、參數(shù)編碼的字符串
返回值 | 參數(shù)1 | 參數(shù)2 | ... | 參數(shù)n |
---|
Type Encoding
iOS中提供了一個叫做@encode的指令刊殉,可以將具體的類型表示成字符串編碼
方法緩存
Class內部結構中有個方法緩存(cache_t),用散列表(哈希表)來緩存曾經調用過的方法州胳,可以提高方法的查找速度.
緩存查找:
-objc-cache.mm
- bucket_t * cache_t::find(cache_key_t k, id receiver)
cache_t cache : Class
內部結構有個方法緩存(cache_t)
,用散列表來緩存曾經調用過的方法,可以提高方法的查找速度.
LDPerson *person = [[LDPerson alloc] init]
[person test]
消息機制:
思路:test
方法為對象方法,存儲在class
對象當中.所以首先會通過instance
的isa
指針查找到LDPerson
的class
對象,然后去cache
方法緩存列表中查找是否有該方法的緩存,如果有直接調用,如果沒有進行下面的操作
然后找到class
對象中的 methodList
(方法列表)進行遍歷查找該方法,如果查找到該方法,調用該方法,并添加到方法緩存cache
中,方便下次調用,省略再次調用遍歷方法列表(二維數(shù)組)的操作,如果在該class
對象中沒有找到該方法,會通過該class
對象的superClasss
指針查找到class
對象的父類 superClass
,再查找到superClass
的cache
方法緩存列表,如果緩存中沒有該方法,則會通過bits --> class_rw_t --->methodList
,進行遍歷操作,沿構造鏈一直向上查找,查找到最底層的baseClass
類,如果還沒找到該方法,就會拋出異常,該方法找不到的錯誤.
注意:類初始化后,第一次調用某方法時就會將方法添加到緩存列表cache
中,二次及以后調用時,會優(yōu)先去cache
中查找是否有該方法的緩存,如果有直接調用,如果沒有重復上述操作.子類對象第一次調用父類方法時,會通過isa
指針向上查找,查找到父類方法后會將該父類方法添加到自己的方法緩存列表中,下次調用時,就不需要superClass
指針查找父類方法了!
cache散列表實現(xiàn)原理:
通過struct bucket_t
結構體中的兩個成員變量:cache_key_t _key(SEL作為key)
和imp _imp
(函數(shù)指針,指向函數(shù)的地址)例如:
LDPerson *person = [[LDPerson alloc] init]
[person test]
@selector(test) & _mask
通過SEL
和結構體cache_t
中的_mask
做與運算,得到一個值,這個值就是該方法test
在cache_t
結構體數(shù)組 _buckets(方法緩存列表)
中的索引.當test
方法第一次被調用時,通過sel&_mask
得到該方法索引,并將該方法存儲到方法緩存數(shù)組的索引位置.當先次調用該方法時,會有優(yōu)先去方法緩存列表中尋找_mask
,通過該方法的SEL&_mask
得到該方法索引位置,然后通過該索引去_buckets
列表中去查找該方法,如果為空,則遍歷LDPerson
的class
對象的方法列表(加入_caches
緩存列表,如果沒有沿構造鏈向上尋找...).如果多個方法的SEL&_mask
得到的索引相等,即該索引位置已經存儲過方法,則將得到的索引位置減1,作為該方法的索引位置.如果減一后的索引位置還是被占用,最終會讓索引值等于Mask
,當_buckets
緩存列表數(shù)組需要擴容時,每次擴容都是當前容量的二倍.擴容時會充值_mask
的值!因為_mask
被重置,方法SEL&_mask
的索引位置發(fā)生變化,已經無法正確獲取到該方法的緩存索引,所以_buckets
會清空緩存,重新開始計算索引位置并緩存方法.
哈希表的核心原理: f(key) == index
,通過一個函數(shù)算法(求余或與運算 或其他運算),傳入一個作為查找的KEY
的得到一個索引位置,如果該索引位置被占用,則可進行其他運算,直到得到一個不被占用的位置.(Apple通過減一操作獲取新索引,如果減一到索引0的位置還是被占用,則設置索引位置為_mask
,如果還是被占用則進行_mask
減一操作,如果都被占用進行擴容操作)并將目標存到該索引位置,就是哈希表的核心原理!