一、objc_msgSend
- 建立命令列專案, 定義
Person
類, 並新增personTest
方法
- 使用終端, 執行下面的命令, 生成
main.m
在底層的main.cpp
檔案
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
複製程式碼
- 其中
[person personTest]
和[Person initialize]
轉化為了下面的程式碼
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("personTest"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("initialize"));
複製程式碼
objc_msgSend(person, sel_registerName("personTest"));
objc_msgSend(objc_getClass("Person"), sel_registerName("initialize"));
複製程式碼
- OC中呼叫方法, 會使用
objc_msgSend
函式給訊息接收者
傳送訊息
- 所以OC呼叫方法的過程也被稱為
訊息機制
- 上面兩個方法呼叫的訊息接收者和訊息名稱如下
objc_msgSend(person, sel_registerName("personTest"));
訊息接收者: person
訊息名稱: personTest
objc_msgSend(objc_getClass("Person"), sel_registerName("initialize"));
訊息接收者: objc_getClass("Person") == [Person class]
訊息名稱: initialize
複製程式碼
二、objc_msgSend底層原始碼
- 可以在官方提供的原始碼中找到
objc_msgSend
的底層實現, 可以看到objc_msgSend
底層是由彙編實現的
1、當訊息接收者為空時, 直接返回, 結束objc_msgSend
函式的呼叫
- 通過原始碼可以看到,
objc_msgSend
執行一開始, 會先判斷訊息接收者
是否為空,
如果為空直接返回
void objc_msgSend(id receiver, SEL selector) {
// 當 訊息接收者 為空時, 直接返回, 結束函式呼叫
if (receiver == nil) return;
}
複製程式碼
2、當訊息接收者有值時, 檢視快取
- 當訊息接收者有值時, 會呼叫
CacheLookup
- 在
CacheLookup
, 首先會查詢cache
快取, 查詢是否已經快取了方法, 如果已經快取會直接呼叫
3、如果方法沒有被快取過, 就會查詢方法列表
- 當方法還沒有快取, 會從方法列表中查詢, 此時呼叫
CheckMiss
, 傳入NORMAL
- 找到
CheckMiss
中NORMAL
部分, 呼叫__objc_msgSend_uncached
- 接著可以看到呼叫了
__class_lookupMethodAndLoadCache3
函式
- 搜尋
__class_lookupMethodAndLoadCache3
函式, 可以發現並沒有__class_lookupMethodAndLoadCache3
的實現部分
- 這主要是因為彙編中的
__class_lookupMethodAndLoadCache3
函式, 在C中的函式名需要去掉一個下劃線_
, 所以函式名應該是_class_lookupMethodAndLoadCache3
- 查詢
_class_lookupMethodAndLoadCache3
, 可以發現_class_lookupMethodAndLoadCache3
函式的實現部分
- 繼續進入
lookUpImpOrForward
函式, 可以看到當傳入cache
為YES
時, 會查詢快取中的方法
- 此時傳入
cache
的是NO
, 所以不會在呼叫cache_getImp
函式從快取中查詢方法
- 然而我們還能看到下面還有從快取中查詢方法的程式碼, 如果找到直接跳轉返回
- 這主要是因為在下圖過程中, 開發者有可能會動態新增一些方法到快取中, 所以才會再次查詢一次
- 當快取中沒有找到需要呼叫的方法時, 就會在方法列表中查詢, 如果找到就會存到快取
cache
中
- 已經知道, 方法存在
cls->rw->methods
中, 而methods
是個二位陣列, 所以需要進行遍歷查詢, 這裡先拿到一維陣列呼叫search_method_list
函式查詢
- 在一維陣列中查詢方法, 這裡有兩種情況
- 第一種: 方法列表已經排好序, 會通過
findMethodInSortedMethodList
函式查詢
- 第二種: 方法列表沒有排好序, 會一個一個遍歷查詢
findMethodInSortedMethodList
函式使用的是二分查詢
的方式查詢方法
- 當找到方法後, 會先將方法儲存到
cache
中, 呼叫log_and_fill_cache
函式進行儲存
log_and_fill_cache
中呼叫cache_fill
函式
cache_fill
函式中呼叫cache_fill_nolock
函式
- 在
cache_fill_nolock
函式中,可以看到方法儲存到了buckets
中
- 如果在自己的類物件中沒有找到需要呼叫的方法, 就會去查詢父類中是否有該方法
- 1.查詢時會一層一層遍歷所有父類, 只要某個父類中找到方法, 就會結束查詢
- 2.先從父類的快取中找, 如果找到, 會先存到自己的
cache
中
- 3.如果父類的快取中沒有該方法, 就會從父類的方法列表中查詢, 如果找到就會存入到自己的
cache
中, 並不會存入到父類的cache
中
- 4.如果沒找到, 就會通過for迴圈檢視父類的父類中有沒有方法, 依次類推, 只要找到就會結束查詢, 並存到自己的
cache
中
- 如果最後還是沒找到, 就會進入下一個階段, 動態解析階段
4、訊息機制流程圖