以下所有內(nèi)容屬筆者原創(chuàng), 如有雷同純屬巧合, 未經(jīng)允許不得轉(zhuǎn)載.
OC中的方法調(diào)用實質(zhì)是發(fā)送消息(objc_msgSend()
)
objc_msgSend()
方法, 默認有2個必傳參數(shù):
- 接收者
- SEL選擇器
objc_msgSend
方法的底層, 蘋果是用匯編語言實現(xiàn)的, 發(fā)送消息以后, 主要是查找方法, 查找方法的步驟是:
-
消息發(fā)送
: 從實例對象對應(yīng)的Class
或者Meta Class
中查找方法 -
動態(tài)方法解析
: 如果步驟1
沒有找到方法, 會開始進行resolve
, 動態(tài)解析. 在這個過程中, 如果開發(fā)者手動添加了方法實現(xiàn), 那么會將方法添加到相應(yīng)的Class
或Meta Class
中, 然后重新進行步驟1
-
消息轉(zhuǎn)發(fā)
: 如果步驟2
執(zhí)行結(jié)束之后, 也沒有找到方法, 開始進行方法轉(zhuǎn)發(fā)(___forwarding___ 方法
), 若到此還沒有查找到方法, 就會報錯不識別方法選擇器
一. 從實例對象對應(yīng)的Class
或者Meta Class
中查找方法
實例對象對應(yīng)的Class
和Meta Class
對象實際結(jié)構(gòu)是一樣的, 只是存儲內(nèi)容的差別而已
當實例對象
或者類對象
調(diào)用方法以后, 會將方法緩存到對應(yīng)的Class
或Meta Class
中.
蘋果特意在此添加了緩存, 提高查找速度. 并且, 不同于普通的for
或while
循環(huán)遍歷, 蘋果針對這片緩存區(qū)采用了散列
算法, 通過空間換時間的方式, 來大大提高了查找效率.
-
緩存查找
: 當發(fā)送消息以后, 馬上從Class
中開始查找方法, 并且優(yōu)先從Cache
中查找 -
方法列表查找
: 若當前Class
對應(yīng)的Cache
中找不到方法, 開始從Class
對應(yīng)的方法列表中查找 -
Super Class查找
: 當前實例對象的Class
或Meta Class
中未查找到方法以后, 會通過Class
或Meta Class
的isa指針
, 找到對應(yīng)的父類, 繼續(xù)進行上述2個步驟, 直至找到方法
二. 動態(tài)方法解析
若第一次
無法正常從Cache
和方法列表
中查到方法, 則會進行動態(tài)方法解析. 一定注意, 這里說的是第一次.
動態(tài)解析:
- 若調(diào)用的是實例方法, 系統(tǒng)會調(diào)用
- (void)resolveInstanceMethod:(SEL)sel
方法, 我們可以在這里, 對sel
進行判斷, 從而利用runtime
的特性動態(tài)添加實例方法 - 若調(diào)用的是類方法, 系統(tǒng)會調(diào)用
- (void)resolveClassMethod:(SEL)sel
方法, 同樣可以在這里, 對sel
進行判斷, 利用runtime
動態(tài)添加類方法 - 在
- (void)resolveInstanceMethod:(SEL)sel
或- (void)resolveClassMethod:(SEL)sel
方法中動態(tài)添加相應(yīng)的實例方法
或類方法
之后, 這個方法實際上被會添加到對應(yīng)的Class
或Meta Class
的方法列表中 - 此時, 系統(tǒng)會執(zhí)行
retry
操作, 第二次從Cache
和Class
方法列表中查找方法, 并緩存到Cache
中. (注意這里說的第二次)
三. 消息轉(zhuǎn)發(fā)
若經(jīng)過動態(tài)方法解析以后, 都沒有找到對應(yīng)的方法, 那么系統(tǒng)會進行最后一步操作, 叫做消息轉(zhuǎn)發(fā)
消息轉(zhuǎn)發(fā)
會先后調(diào)用以下幾個方法:
-
forwardingTargetForSelector:
返回值不為nil, 調(diào)用objc_msgSend(返回值, SEL)
; 返回值為nil, 執(zhí)行下一步 -
methodSignatureForSelector:
返回值不為nil, 調(diào)用forwardInvocation:
方法; 返回值為nil, 執(zhí)行下一步 doesNotRecognizeSelector: