上一節(jié)已了解類的cache結(jié)構(gòu)
和插入
操作柴梆。但是有幾個(gè)問題:
- 1. 何時(shí)
插入
緩存偷办? - 2. 緩存
讀取
機(jī)制是怎樣盆赤?
現(xiàn)在開始探索之旅
1. 探索插入操作
2. 介紹Runtime
3. 了解方法的本質(zhì)
4. objc_msgSend解析
1. 探索插入操作
我們從insert
開始尋找誰(shuí)在調(diào)用它
- 在objc4源碼下搜索
->insert(
(c++
的調(diào)用方式->
)
- 發(fā)現(xiàn)
cache_fill
調(diào)用了它搓译,我們繼續(xù)搜索cache_fill
:
我們發(fā)現(xiàn)措伐,在緩存寫入
之前烙懦,我們需要先知道:
-
給誰(shuí)
進(jìn)行寫入
(objc_msgSend
) -
寫入
內(nèi)容是什么
(cache_getImp
)
到這里铺韧,我們必須引出OC非常重要的機(jī)制:Runtime運(yùn)行時(shí)
2. 介紹Runtime
?? Objective-C Runtime Programming Guide,此文檔
不再更新
蟹略,適合初步了解Runtime
2.1 什么是Runtime
- Runtime是一個(gè)由
C
登失、C++
、匯編
混合開發(fā)的API
庫(kù)挖炬,它將程序
的一些決定性工作
從編譯器
推遲到運(yùn)行期
揽浙,使得OC語(yǔ)言
具備動(dòng)態(tài)
特性。內(nèi)部使用消息機(jī)制
進(jìn)行通信
意敛。
2.2 什么是運(yùn)行時(shí)? 什么是編譯時(shí)馅巷?
-
編譯時(shí)
:編譯器將源代碼
翻譯成機(jī)器
能識(shí)別
的代碼
。這是一個(gè)靜態(tài)操作
草姻,并不會(huì)
把代碼寫入內(nèi)存
中進(jìn)行運(yùn)行
钓猬。
- 編譯過(guò)程中,會(huì)
分析語(yǔ)法
是否正確撩独。- 編譯時(shí)提示的
error
敞曹、warning
都是編譯時(shí)錯(cuò)誤- 編譯過(guò)程檢查就叫
編譯時(shí)類型檢查
或靜態(tài)類型檢查
-
運(yùn)行時(shí)
: 將代碼裝載
入內(nèi)存
中账月,讓代碼運(yùn)行
起來(lái)
- 代碼在
裝載
入內(nèi)存
之前,只是個(gè)"死家伙"
澳迫,靜靜地趴在磁盤中局齿。只有載入內(nèi)存
,才是"活的"
橄登。運(yùn)行時(shí)類型檢查
與前面所說(shuō)的編譯時(shí)類型檢查
(或叫靜態(tài)類型檢查
)不一樣抓歼,它不是簡(jiǎn)單的掃描代碼,而是在內(nèi)存中
做些操作
拢锹,做些判斷
谣妻。(是動(dòng)態(tài)活動(dòng)的
)
例如一個(gè)函數(shù),只聲明
面褐,未實(shí)現(xiàn)
拌禾。 command+B
編譯時(shí)不會(huì)報(bào)錯(cuò),但是command+R
運(yùn)行時(shí)會(huì)報(bào)錯(cuò)展哭。
2.3 Runtime版本
-
Runtime
有兩個(gè)版本湃窍,Legacy
(早期版本) 和Modern
(現(xiàn)行版本)
早期
版本:Objective-C 1.0
,用于32位
的Mac OS X
的平臺(tái)上匪傍,實(shí)例變量發(fā)生改變后您市,需要重新編譯其子類
。現(xiàn)行
版本:Objective-C 2.0
役衡,用于iPhone
程序和Mac OS X v10.5
及以后
的系統(tǒng)中的64 位程序
茵休,實(shí)例變量發(fā)生改變后,不需要重新編譯其子類
手蝎。
2.4 運(yùn)行時(shí)讓OC具備多態(tài)
特性
OC的運(yùn)行時(shí)機(jī)制:將
數(shù)據(jù)類型
的確定由編譯時(shí)
榕莺,推遲到運(yùn)行時(shí)
。OC的這種運(yùn)行時(shí)機(jī)制使對(duì)象的類型
及對(duì)象的屬性
和方法
在運(yùn)行時(shí)
才能確定
棵介。多態(tài)
:不同對(duì)象
以自己的方式響應(yīng)相同的消息
的能力叫做多態(tài)
例如:
自然界中的人類(Person
)都有一個(gè)相同的方法-sing
钉鸯,男人(Man
)類屬于人類
,女人(Wonan
)類也屬于人類
邮辽,都繼承
了人類后唠雕,會(huì)實(shí)現(xiàn)各自的-sing
方法。但是自然界中男人和女人的sing
的風(fēng)格
又不一樣吨述,男人
唱的豪邁
岩睁,女人
唱的委婉
,但都繼承了person唱的能力揣云,這就是多態(tài)
的現(xiàn)象捕儒。
也就是不同的對(duì)象
以自己的方式響應(yīng)相同消息
的能力叫多態(tài)
。 也可以說(shuō)運(yùn)行時(shí)
機(jī)制是多態(tài)
的基礎(chǔ)
邓夕。
描述參考??: 碼上江湖
2.5 Runtime的三種調(diào)用
-
oc代碼調(diào)用刘莹、framework調(diào)用 亿笤、RuntimeAPI調(diào)用
image.png
2.6 探索runtime
先準(zhǔn)備好靜態(tài)資源
的,咱們才能動(dòng)態(tài)
起來(lái)栋猖。所以先獲取compiler
層文件。
- 測(cè)試代碼:
@interface HTPerson : NSObject
- (void)sayHello;
@end
@implementation HTPerson
- (void)sayHello{
NSLog(@"%s",__func__);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
HTPerson *p = [HTPerson alloc];
[p sayHello];
}
return 0;
}
-
clang
靜態(tài)編譯main.m
文件:clang -rewrite-objc main.m -o main.cpp
我們發(fā)現(xiàn)汪榔,所有OC方法
蒲拉,不管是類方法alloc
,還是實(shí)例方法sayHello
都是調(diào)用了objc_msgSend
發(fā)送消息痴腌。
調(diào)用格式:
objc_msgSend: (消息接收者, 消息主體)
嘗試手動(dòng)使用
objc_msgSend
執(zhí)行方法:
- 導(dǎo)入頭文件
#import <objc/message.h>
- 手動(dòng)
關(guān)閉
運(yùn)行時(shí)的編譯警告
:buildSetiing
->Enable Strict Checking of objc_msgSend Calls
->設(shè)置為No
- 加入測(cè)試代碼
objc_msgSend(p, sel_registerName("sayHello"));
- 打印查看:
image.png
崩潰
暫時(shí)不理會(huì)雌团,我們現(xiàn)在發(fā)現(xiàn)sayHello
打印成功了
3. 了解方法的本質(zhì)
我們知道,方法的本質(zhì)
就是一個(gè)方法名
和對(duì)應(yīng)的函數(shù)代碼
士聪。
OC
中锦援,我們使用執(zhí)行對(duì)象
+函數(shù)名
進(jìn)行函數(shù)調(diào)用 (例如:[person sayHello]
)。內(nèi)部完整的調(diào)用流程是:
objc_msgSend
發(fā)送消息(class sel) -> 通過(guò)sel
(方法編號(hào))找到imp
(函數(shù)指針地址) -> 找到函數(shù)內(nèi)容
- 第一步: 發(fā)送消息我們有三種API調(diào)用方法(
oc代碼調(diào)用剥悟、framework調(diào)用 灵寺、RuntimeAPI調(diào)用
)- 第三步:可直接 從
函數(shù)指針地址
讀取函數(shù)內(nèi)容
。
上述流程区岗,我們唯一不知道的就是系統(tǒng)
如何通過(guò)sel
(方法編號(hào))找到imp
(函數(shù)指針地址)略板?
4. objc_msgSend解析
了解sel
如何找到imp
就是探究 objc_msgSend
內(nèi)部機(jī)制。
函數(shù)的調(diào)用是極其頻繁的慈缔,所以對(duì)
性能
的要求非常高叮称。objc_msgSend
使用匯編
進(jìn)行編寫
。imp
的查找分為2個(gè)階段藐鹤,快速查找
(緩存cache
中瓤檐,匯編編寫
)和慢速查找
(方法列表methodTable
中,c和c++編寫
)娱节,今天先介紹快速查找
接下來(lái)的知識(shí)挠蛉,需要大家先熟悉cache_t的結(jié)構(gòu)
匯編
查找函數(shù)的流程: 從指定類開始
->定位cache
->定位buckets
->哈希運(yùn)算獲取首次位置
->循環(huán)尋找位置
->返回imp
或null
打開
objc4
源碼,搜索objc_msgsend
括堤,我們選擇arm64
真機(jī)環(huán)境進(jìn)行探索(其他環(huán)境也是類似邏輯)碌秸。找到
objc_msgSend入口
:
-
初始化數(shù)據(jù)
, 從receiver
中讀取isa
中的類
悄窃。
image.png 當(dāng)前
objc-msg-arm64.s
匯編文件中搜索CacheLookup
讥电,找到.macro CacheLookup
定義處:
- 如果
匹配sel成功
,調(diào)用Cachehit
命中緩存流程, 返回找到的imp
-
如果
匹配失敗
,觸發(fā)CheckMiss
和JumpMiss
流程, 告知外部Cache
中未找到imp
image.png 未找到
imp
時(shí)轧抗,進(jìn)入__objc_msgSend_uncached
流程恩敌,搜索__objc_msgSend_uncached
:
- 發(fā)現(xiàn)
緩存找不到
后,進(jìn)入方法列表
去查找横媚。 搜索MethodTableLookup
:
- 跳轉(zhuǎn)
_lookUpImpOrForward
纠炮,進(jìn)入慢速查找
階段月趟。
奉上完整流程
下一節(jié): OC底層原理十三: objc_msgSend(方法慢速查找)