Runtime系列文章
Runtime原理探究(一)—— isa的深入體會(huì)(蘋(píng)果對(duì)isa的優(yōu)化)
Runtime原理探究(二)—— Class結(jié)構(gòu)的深入分析
Runtime原理探究(三)—— OC Class的方法緩存cache_t
Runtime原理探究(四)—— 刨根問(wèn)底消息機(jī)制
Runtime原理探究(五)—— super的本質(zhì)
[Runtime原理探究(六)—— Runtime的應(yīng)用...待續(xù)]-()
[Runtime原理探究(七)—— Runtime的API...待續(xù)]-()
Runtime原理探究(八)—— 面試題中的Runtime
上一篇里面渣磷,我們從Class
的cache_t
作為切入點(diǎn)圈盔,完善了我們對(duì)于OC類對(duì)象的認(rèn)識(shí),而且還詳細(xì)了解了Runtime的消息發(fā)送流程和方法緩存策略付魔,不過(guò)對(duì)于消息機(jī)制這個(gè)話題只是熱身而已逢渔。接下來(lái)肋坚,本文就從源頭開(kāi)始,完整地來(lái)研究Runtime的消息機(jī)制肃廓。
消息機(jī)制流程框架
[obj message] ?? 消息發(fā)送 ?? 動(dòng)態(tài)方法解析 ?? 消息轉(zhuǎn)發(fā)
(一)消息發(fā)送
消息發(fā)送流程上一篇文章已經(jīng)分析過(guò)智厌,這里再?gòu)?code>[obj message]為出發(fā)點(diǎn),從objc源碼里進(jìn)行一次正向梳理盲赊。
首先铣鹏,要查看[obj message]
的底層表示,可以通過(guò)xcode調(diào)試工具調(diào)出其匯編代碼進(jìn)行分析哀蘑,但是這個(gè)方法需要你至少有熟練的匯編代碼閱讀能力诚卸,有不少難度葵第。如果把要求降低一點(diǎn),可以在命令行工具里面通過(guò)
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc xxx.m -o yyy.cpp
生成一個(gè)中間代碼合溺,這個(gè)中間代碼基本上都是C或C++代碼羹幸,閱讀起來(lái)相對(duì)容易。但是需要說(shuō)明一下辫愉,這個(gè)中間代碼僅作為參考,因?yàn)槟壳皒code編譯器已經(jīng)不使用用這種格式的中間代碼的将硝,取而代之的是另一種語(yǔ)法格式的中間代碼恭朗,但是雖然語(yǔ)法不同,但是實(shí)現(xiàn)思路和邏輯大致是相同的依疼,因此老的中間代碼還是能夠借來(lái)參考一下的痰腮。
通過(guò)上面的命令行操作,[obj message]
編譯之后的底層表示是
((void (*)(id, SEL))(void *)objc_msgSend)((id)obj, sel_registerName("message"));
去掉類型轉(zhuǎn)換后律罢,簡(jiǎn)化一下可以表示成
objc_msgSend(obj, sel_registerName("message"));
其中第一個(gè)參數(shù)obj
就是消息接受者膀值,后面的sel_registerName
,可以在objc
源碼中搜到它的函數(shù)聲明
SEL _Nonnull sel_registerName(const char * _Nonnull str)
很明顯這里是根據(jù)一個(gè)C字符串返回一個(gè)SEL
误辑,其實(shí)就等同于OC里面的@selector()
沧踏。這兩個(gè)參數(shù)都沒(méi)什么太大疑問(wèn),然后我們來(lái)從源碼里看一看能否找到objc_msgSend
的實(shí)現(xiàn)巾钉。但最終翘狱,你無(wú)法在源碼里面找到對(duì)應(yīng)的C函數(shù)實(shí)現(xiàn),因?yàn)樵趏bjc源碼里面砰苍,是通過(guò)匯編來(lái)實(shí)現(xiàn)的潦匈,并且對(duì)應(yīng)不同架構(gòu)有不同的版本,這里我們就關(guān)注arm64版本的實(shí)現(xiàn)赚导。
msg
吼旧,還有涉及到block
相關(guān)的一些內(nèi)容也是用匯編實(shí)現(xiàn)的凰锡,還有一個(gè)objc-sel-table.s
,受限本人知識(shí)儲(chǔ)備黍少,暫時(shí)還解讀不了寡夹,不過(guò)沒(méi)關(guān)系,它跟我們現(xiàn)在討論的話題不相關(guān)厂置。
你或許會(huì)疑惑菩掏,蘋(píng)果為什么要用匯編來(lái)實(shí)現(xiàn)某些函數(shù)呢?主要原因是因?yàn)閷?duì)于一些調(diào)用頻率太高的函數(shù)或操作昵济,使用匯編來(lái)實(shí)現(xiàn)能夠提高效率智绸。在匯編源碼里面野揪,可以按照下面的方法來(lái)定位函數(shù)實(shí)現(xiàn)
接下來(lái)我們開(kāi)始閱讀匯編
然后查找一下CacheLookup,看看緩存怎么查詢的瞧栗,注意斯稳,這里的NORMAL是參數(shù)。
如果是命中緩存迹恐,找到了方法挣惰,那就簡(jiǎn)單了,直接返回并調(diào)用就好了殴边,如果沒(méi)找憎茂,就會(huì)進(jìn)入上圖中的__objc_msgSend_uncached
__objc_msgSend_uncached
中調(diào)用了MethodTableLookup
MethodTableLookup
里面,調(diào)用了__class_lookupMethodAndLoadCache3
函數(shù)锤岸,而這個(gè)函數(shù)在當(dāng)前的匯編代碼里面是找不到實(shí)現(xiàn)的竖幔。你去objc源碼進(jìn)行全局搜索,也搜不到是偷,這里經(jīng)過(guò)大佬指點(diǎn)拳氢,如果是一個(gè)C函數(shù),在底層匯編里面如果需要調(diào)用的話蛋铆,蘋(píng)果會(huì)為其加一個(gè)下劃線_
馋评,因此上面的的函數(shù)刪去一個(gè)下劃線,_class_lookupMethodAndLoadCache3
戒职,你就可以在源碼里面找到它對(duì)應(yīng)的C函數(shù)栗恩,它是objc-runtime-new.mm
里面的一個(gè)C函數(shù)
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
而這個(gè)函數(shù)里面最終是調(diào)用了lookUpImpOrForward
函數(shù),從這個(gè)函數(shù)開(kāi)始的后面的流程洪燥,我在上一篇文章里面已經(jīng)做過(guò)完整解讀了磕秤,這里不再做詳細(xì)論述,只將之前的結(jié)論貼出來(lái)
- (1) 當(dāng)一個(gè)對(duì)象接收到消息時(shí)
[obj message];
捧韵,首先根據(jù)obj
的isa
指針進(jìn)入它的類對(duì)象cls
里面市咆。- (2) 在
obj
的cls
里面,首先到緩存cache_t
里面查詢方法message
的函數(shù)實(shí)現(xiàn)再来,如果找到蒙兰,就直接調(diào)用該函數(shù)。- (3) 如果上一步?jīng)]有找到對(duì)應(yīng)函數(shù)芒篷,在對(duì)該
cls
的方法列表進(jìn)行二分/遍歷查找搜变,如果找到了對(duì)應(yīng)函數(shù),首先會(huì)將該方法緩存到obj
的類對(duì)象cls
的cache_t
里面针炉,然后對(duì)函數(shù)進(jìn)行調(diào)用挠他。- (4) 在每次進(jìn)行緩存操作之前,首先需要檢查緩存容量篡帕,如果緩存內(nèi)的方法數(shù)量超過(guò)規(guī)定的臨界值(
設(shè)定容量的3/4
)殖侵,需要先對(duì)緩存進(jìn)行2倍擴(kuò)容贸呢,原先緩存過(guò)的方法全部丟棄,然后將當(dāng)前方法存入擴(kuò)容后的新緩存內(nèi)拢军。- (5) 如果在
obj
的cls
對(duì)象里面楞陷,發(fā)現(xiàn)緩存和方法列表都找不到mssage
方法,則通過(guò)cls
的superclass
指針進(jìn)入它的父類對(duì)象f_cls
里面- (6) 進(jìn)入
f_cls
后茉唉,首先在它的cache_t
里面查找mssage
固蛾,如果找到了該方法,那么會(huì)首先將方法緩存到消息接受者obj
的類對(duì)象cls
的cache_t
里面度陆,然后調(diào)用方法對(duì)應(yīng)的函數(shù)魏铅。- (7) 如果上一步?jīng)]有找到方法,將會(huì)對(duì)
f_cls
的方法列表進(jìn)行遍歷二分/遍歷查找坚芜,如果找到了mssage
方法,那么同樣斜姥,會(huì)首先將方法緩存到消息接受者obj
的類對(duì)象cls
的cache_t
里面鸿竖,然后調(diào)用方法對(duì)應(yīng)的函數(shù)。需要注意的是铸敏,這里并不會(huì)將方法緩存到當(dāng)前父類對(duì)象f_cls
的cache_t里面缚忧。- (8) 如果還沒(méi)找到方法,則會(huì)通過(guò)
f_cls
的superclass
進(jìn)入更上層的父類對(duì)象里面杈笔,按照(6)->(7)->(8)
步驟流程重復(fù)闪水。如果此時(shí)已經(jīng)到了基類對(duì)象NSObject
,仍沒(méi)有找到mssage
蒙具,則進(jìn)入步驟(9)
- (9) 接下來(lái)將會(huì)轉(zhuǎn)到消息機(jī)制的 動(dòng)態(tài)方法解析 階段
消息發(fā)送流程
到此球榆,消息發(fā)送機(jī)制的正向解讀就到這里。關(guān)于上面的匯編代碼禁筏,我自己也只是借助相關(guān)注釋說(shuō)明持钉,間接挖掘蘋(píng)果的底層思路,其實(shí)匯編里面還有更多的細(xì)節(jié)篱昔,只有你自己親自讀一遍每强,才會(huì)有更跟深的體會(huì)和領(lǐng)悟。
(二)動(dòng)態(tài)方法解析
接下來(lái)州刽,一起來(lái)認(rèn)識(shí)一下方法的動(dòng)態(tài)解析空执。上面的章節(jié),我們講到了lookUpImpOrForward
函數(shù)穗椅,這個(gè)函數(shù)我在之前的文章也具體討論過(guò)了辨绊,但是僅僅是解讀完了消息發(fā)送和方法緩存的內(nèi)容,這里我先貼出該函數(shù)的代碼
/***********************************************************************
* lookUpImpOrForward.
* The standard IMP lookup. ------------->??????標(biāo)準(zhǔn)的IMP查找流程
* initialize==NO tries to avoid +initialize (but sometimes fails)
* cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)
* Most callers should use initialize==YES and cache==YES.
* inst is an instance of cls or a subclass thereof, or nil if none is known.
* If cls is an un-initialized metaclass then a non-nil inst is faster.
* May return _objc_msgForward_impcache. IMPs destined for external use
* must be converted to _objc_msgForward or _objc_msgForward_stret.
* If you don't want forwarding at all, use lookUpImpOrNil() instead.
**********************************************************************/
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
// Optimistic cache lookup
if (cache) {//------------------>??????查詢當(dāng)前Class對(duì)象的緩存房待,如果找到方法邢羔,就返回該方法
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
// runtimeLock is held during isRealized and isInitialized checking
// to prevent races against concurrent realization.
// runtimeLock is held during method search to make
// method-lookup + cache-fill atomic with respect to method addition.
// Otherwise, a category could be added but ignored indefinitely because
// the cache was re-filled with the old value after the cache flush on
// behalf of the category.
runtimeLock.read();
if (!cls->isRealized()) {//--------------->??????當(dāng)前Class如果沒(méi)有被realized驼抹,就進(jìn)行realize操作
// Drop the read-lock and acquire the write-lock.
// realizeClass() checks isRealized() again to prevent
// a race while the lock is down.
runtimeLock.unlockRead();
runtimeLock.write();
realizeClass(cls);
runtimeLock.unlockWrite();
runtimeLock.read();
}
if (initialize && !cls->isInitialized()) {//--------->??????當(dāng)前Class如果沒(méi)有初始化,就進(jìn)行初始化操作
runtimeLock.unlockRead();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.read();
// If sel == initialize, _class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}
retry:
runtimeLock.assertReading();
// Try this class's cache.//------------>??????嘗試從該Class對(duì)象的緩存中查找拜鹤,如果找到框冀,就跳到done處返回該方法
imp = cache_getImp(cls, sel);
if (imp) goto done;
// Try this class's method lists.//---------------->??????嘗試從該Class對(duì)象的方法列表中查找,找到的話敏簿,就緩存到該Class的cache_t里面明也,并跳到done處返回該方法
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
// Try superclass caches and method lists.------>??????進(jìn)入當(dāng)前Class對(duì)象的superclass對(duì)象
{
unsigned attempts = unreasonableClassCount();
for (Class curClass = cls->superclass;//------>??????該for循環(huán)每循環(huán)一次,就會(huì)進(jìn)入上一層的superclass對(duì)象惯裕,進(jìn)行循環(huán)內(nèi)部方法查詢流程
curClass != nil;
curClass = curClass->superclass)
{
// Halt if there is a cycle in the superclass chain.
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.------>??????在當(dāng)前superclass對(duì)象的緩存進(jìn)行查找
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this class.
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;//------>??????如果在當(dāng)前superclass的緩存里找到了方法温数,就調(diào)用log_and_fill_cache進(jìn)行方法緩存,注意這里傳入的參數(shù)是cls蜻势,也就是將方法緩存到消息接受對(duì)象所對(duì)應(yīng)的Class對(duì)象的cache_t中撑刺,然后跳到done處返回該方法
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;//---->??????如果緩存里找到的方法是_objc_msgForward_impcache,就跳出該輪循環(huán)握玛,進(jìn)入上一層的superclass够傍,再次進(jìn)行查找
}
}
// Superclass method list.---->??????如過(guò)畫(huà)緩存里面沒(méi)有找到方法,則對(duì)當(dāng)前superclass的方法列表進(jìn)行查找
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
//------>??????如果在當(dāng)前superclass的方法列表里找到了方法挠铲,就調(diào)用log_and_fill_cache進(jìn)行方法緩存冕屯,注意這里傳入的參數(shù)是cls,也就是將方法緩存到消息接受對(duì)象所對(duì)應(yīng)的Class對(duì)象的cache_t中拂苹,然后跳到done處返回該方法
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
//**********************???消息發(fā)送流程結(jié)束???*************************
//??????動(dòng)態(tài)方法解析
// No implementation found. Try method resolver once.//------>??????如果到基類還沒(méi)有找到方法安聘,就嘗試進(jìn)行方法解析
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
_class_resolveMethod(cls, sel, inst);
runtimeLock.read();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}
//??????消息轉(zhuǎn)發(fā)
// No implementation found, and method resolver didn't help. //------>??????如果方法解析不成功,就進(jìn)行消息轉(zhuǎn)發(fā)
// Use forwarding.
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlockRead();
return imp;
}
根據(jù)上面代碼里面的??????動(dòng)態(tài)方法解析
標(biāo)記處瓢棒,我們繼續(xù)解讀消息機(jī)制的 動(dòng)態(tài)方法解析階段浴韭。
首先注意一個(gè)細(xì)節(jié),這里有一個(gè)標(biāo)簽triedResolver
用來(lái)判斷是否進(jìn)行該類是否進(jìn)行過(guò)動(dòng)態(tài)方法解析脯宿。如果首次走到這里囱桨,triedResolver = NO
,當(dāng)動(dòng)態(tài)方法解析進(jìn)行過(guò)一次之后嗅绰,會(huì)設(shè)置triedResolver = YES
舍肠,這樣下次走到這里的時(shí)候,就不會(huì)再次進(jìn)行動(dòng)態(tài)方法解析窘面,因?yàn)檫@個(gè)流程只需要進(jìn)行一次就夠了翠语,并且實(shí)在首次調(diào)用一個(gè)該類沒(méi)有實(shí)現(xiàn)的方法的時(shí)候,才會(huì)進(jìn)行這個(gè)流程财边,仔細(xì)體會(huì)一下
goto retry
回到的地方是本函數(shù)的如下位置【新增的方法會(huì)存放在 消息接受者->ISA() 的rw的方法列表里面去】
),會(huì)重新走一遍緩存查找和消息發(fā)送谍夭。
下面再繼續(xù)看一下方法動(dòng)態(tài)解析里面的核心函數(shù)_class_resolveMethod(cls, sel, inst);
***********************************************************************
* _class_resolveMethod
* Call +resolveClassMethod or +resolveInstanceMethod.
* Returns nothing; any result would be potentially out-of-date already.
* Does not check if the method already exists.
**********************************************************************/
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
很明顯黑滴,if (! cls->isMetaClass())
這句代碼實(shí)在判斷當(dāng)前的參數(shù)cls
是否是一個(gè)meta-class
對(duì)象,也就是說(shuō)紧索,調(diào)用對(duì)象方法(-方法
)和調(diào)用類方法(+方法
)過(guò)程里面的動(dòng)態(tài)方法解析袁辈,走的都是這個(gè)方法啊,這里我們先關(guān)注對(duì)象方法(-方法
)的解析處理邏輯珠漂。也就是_class_resolveInstanceMethod(cls, sel, inst);
晚缩,進(jìn)入它的函數(shù)實(shí)現(xiàn)如下
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
//---??????查看cls的meta-class對(duì)象的方法列表里面是否有SEL_resolveInstanceMethod函數(shù),
//---??????也就是看是否實(shí)現(xiàn)了+(BOOL)resolveInstanceMethod:(SEL)sel方法
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
//---??????如果沒(méi)找到媳危,直接返回荞彼,
// Resolver not implemented.
return;
}
//---??????如果找到,則通過(guò)objc_msgSend調(diào)用一下+(BOOL)resolveInstanceMethod:(SEL)sel方法
//---??????完成里面的動(dòng)態(tài)增加方法的步驟
//---??????接下來(lái)是一些對(duì)解析結(jié)果的打印信息
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
IMP imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
動(dòng)態(tài)方法解析的核心步驟完成之后待笑,會(huì)一層一層往上返回到lookUpImpOrForward
函數(shù)鸣皂,跳到retry
標(biāo)記處,重新查詢方法暮蹂,因?yàn)樵诜椒ń馕鲞@一步签夭,如果對(duì)某個(gè)目標(biāo)方法名xxx
有過(guò)處理,為其動(dòng)態(tài)增加了方法實(shí)現(xiàn)椎侠,那么再次查詢?cè)摲椒ǎ瑒t一定可以在消息發(fā)送階段被找到并調(diào)用措拇。 對(duì)于類方法(+方法
)的動(dòng)態(tài)解析其實(shí)跟上面的過(guò)程大致相同我纪,只不過(guò)解析的時(shí)候調(diào)用的+(BOOL)resolveClassMethod:(SEL)sel
方法,來(lái)完成類方法的動(dòng)態(tài)添加綁定丐吓。
小結(jié)
首先用圖來(lái)總結(jié)一下動(dòng)態(tài)方法解析
動(dòng)態(tài)方法解析真的有必要嗎浅悉?
其實(shí)這個(gè)我覺(jué)得沒(méi)有固定答案,根據(jù)個(gè)人的理解因人而異券犁,就我個(gè)人的膚淺看法术健,好像除了在面試?yán)锩婵梢栽黾右稽c(diǎn)逼格外,實(shí)際項(xiàng)目中好像沒(méi)太多使用場(chǎng)景粘衬,因?yàn)榕c其在動(dòng)態(tài)解析步驟里面動(dòng)態(tài)增加方法荞估,還不如直接在類里面實(shí)現(xiàn)該方法呢,不知道大家有什么心得體會(huì)稚新,歡迎留言交流勘伺。
好了,動(dòng)態(tài)方法解析流程解讀完畢褂删。
(三)消息轉(zhuǎn)發(fā)
經(jīng)過(guò)前兩個(gè)流程之后飞醉,如果還沒(méi)能找到方法對(duì)應(yīng)的函數(shù),說(shuō)明當(dāng)前類已經(jīng)盡力了屯阀,但是確實(shí)沒(méi)有能力處理目標(biāo)方法缅帘,因子只能把方法拋給別人轴术,也就丟給其他的類去處理,因此最后一個(gè)流程為什么叫消息轉(zhuǎn)發(fā)钦无,顧名思義逗栽。
下面,我們來(lái)搞定消息轉(zhuǎn)發(fā)铃诬,入口如下祭陷,位于lookUpImpOrForward
函數(shù)的尾部
(IMP)_objc_msgForward_impcache
指針趣席。對(duì)源碼搜索一下兵志,發(fā)現(xiàn)它其實(shí)也是一段匯編實(shí)現(xiàn)
STATIC_ENTRY __objc_msgForward_impcache
MESSENGER_START
nop
MESSENGER_END_SLOW
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
//**************************************************************
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr x17, [x17, __objc_forward_handler@PAGEOFF]
br x17
END_ENTRY __objc_msgForward
匯編中的調(diào)用順序是這樣
_objc_msgForward_impcache
->__objc_msgForward
->__objc_forward_handler
,我們可以嘗試搜索一下objc_forward_handler
,最終宣肚,我們可以在objc-runtime.mm
里面可以找到與objc_forward_handler
相關(guān)的信息
__attribute__((noreturn)) void
objc_defaultForwardHandler(id self, SEL sel)
{
_objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
"(no message forward handler is installed)",
class_isMetaClass(object_getClass(self)) ? '+' : '-',
object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
發(fā)現(xiàn)_objc_forward_handler
其實(shí)是一個(gè)函數(shù)指針想罕,指向objc_defaultForwardHandler
,但是這個(gè)函數(shù)只有打印信息霉涨,再往下深入按价,無(wú)法看出消息轉(zhuǎn)發(fā)更底層的執(zhí)行邏輯,蘋(píng)果對(duì)此并沒(méi)有開(kāi)源笙瑟。如果想要繼續(xù)挖掘楼镐,就只通過(guò)匯編碼逆向反推C函數(shù)的實(shí)現(xiàn),逆向是一個(gè)很大的話題往枷,需要很多知識(shí)儲(chǔ)備框产,本文無(wú)法展開(kāi)介紹。
其實(shí)错洁,如果消息機(jī)制的前兩個(gè)流程都沒(méi)命中秉宿,進(jìn)入消息轉(zhuǎn)發(fā)階段,則會(huì)調(diào)用__forwarding__
函數(shù)屯碴。這個(gè)可以從xcode的打印信息里面驗(yàn)證描睦,如果調(diào)用一個(gè)沒(méi)有實(shí)現(xiàn)的方法,并且動(dòng)態(tài)解析和消息轉(zhuǎn)發(fā)都沒(méi)有處理导而,最終打印結(jié)果如下
可以看到從底層上來(lái)忱叭,調(diào)用了CF框架的
_CF_forwarding_prep_0
,然后就調(diào)用了___forwarding___
今艺。該函數(shù)就屬于蘋(píng)果未開(kāi)源部分窑多,感謝大神MJ老師的分享,以下貼出他為我提供的一份消息轉(zhuǎn)發(fā)流程的C函數(shù)實(shí)現(xiàn)洼滚,
int __forwarding__(void *frameStackPointer, int isStret) {
id receiver = *(id *)frameStackPointer;
SEL sel = *(SEL *)(frameStackPointer + 8);
const char *selName = sel_getName(sel);
Class receiverClass = object_getClass(receiver);
// 調(diào)用 forwardingTargetForSelector:
if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
id forwardingTarget = [receiver forwardingTargetForSelector:sel];
if (forwardingTarget && forwardingTarget != receiver) {
return objc_msgSend(forwardingTarget, sel, ...);
}
}
// 調(diào)用 methodSignatureForSelector 獲取方法簽名后再調(diào)用 forwardInvocation
if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
if (methodSignature && class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer];
[receiver forwardInvocation:invocation];
void *returnValue = NULL;
[invocation getReturnValue:&value];
return returnValue;
}
}
if (class_respondsToSelector(receiverClass,@selector(doesNotRecognizeSelector:))) {
[receiver doesNotRecognizeSelector:sel];
}
// The point of no return.
kill(getpid(), 9);
}
__forwarding__
函數(shù)邏輯可以簡(jiǎn)單概括成
forwardingTargetForSelector:
->methodSignatureForSelector
->forwardInvocation
埂息。我在功過(guò)一個(gè)流程圖來(lái)解讀一下上面的代碼
-(id)forwardingTargetForSelector:(SEL)aSelector
——__forwarding__
首先會(huì)看類有沒(méi)有實(shí)現(xiàn)這個(gè)方法,這個(gè)方法返回的是一個(gè)id
類型的轉(zhuǎn)發(fā)對(duì)象forwardingTarget
,如果其不為空千康,則會(huì)通過(guò)objc_msgSend
函數(shù)對(duì)其直接發(fā)送消息objc_msgSend(forwardingTarget, sel, ...);
享幽,也就是說(shuō)讓轉(zhuǎn)發(fā)對(duì)象forwardingTarget
去處理當(dāng)前的方法SEL。如果forwardingTarget
為nil
拾弃,則進(jìn)入下面的方法-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
——這個(gè)方法是讓我們根據(jù)方法選擇器SEL
生成一個(gè)NSMethodSignature方法簽名
并返回值桩,這個(gè)方法簽名里面其實(shí)就是封裝了返回值類型,參數(shù)類型的信息豪椿。
__forwarding__
會(huì)利用這個(gè)方法簽名奔坟,生成一個(gè)NSInvocation
,將其作為參數(shù)搭盾,調(diào)用- (void)forwardInvocation:(NSInvocation *)anInvocation
方法咳秉。如果我們?cè)谶@里沒(méi)有返回方法簽名,系統(tǒng)則認(rèn)為我們徹底不想處理這個(gè)方法了鸯隅,就會(huì)調(diào)用doesNotRecognizeSelector:
方法拋出經(jīng)典的報(bào)錯(cuò)報(bào)錯(cuò)unrecognized selector sent to instance 0xXXXXXXXX
澜建,結(jié)束消息機(jī)制的全部流程。- (void)forwardInvocation:(NSInvocation *)anInvocation
——如果我們?cè)谏厦嫣峁┝朔椒ê灻?code>__forwarding__則會(huì)最終調(diào)用這個(gè)方法蝌以。在這個(gè)方法里面炕舵,我們會(huì)拿到一個(gè)參數(shù)(NSInvocation *)anInvocation
,這個(gè)anInvocation
其實(shí)是__forwarding__
對(duì)如下三個(gè)信息的封裝:
anInvocation.target
-- 方法調(diào)用者anInvocation.selector
-- 方法名- (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx;
-- 方法參數(shù)
因此在此方法里面跟畅,我們可以決定將消息轉(zhuǎn)發(fā)給誰(shuí)(target
)咽筋,甚至還可以修改消息的參數(shù),由于anInvocation
會(huì)存儲(chǔ)消息selector
里面帶來(lái)的參數(shù)徊件,并且可以根據(jù)消息所對(duì)應(yīng)的方法簽名確定消息參數(shù)的個(gè)數(shù)奸攻,所以我們通過(guò)- (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx;
可以對(duì)參數(shù)進(jìn)行修改。總之你可以按照你的意愿庇忌,配置好anInvocation
,然后簡(jiǎn)單一句[anInvocation invoke];
即可完成消息的轉(zhuǎn)發(fā)調(diào)用舰褪,也可以不做任何處理皆疹,輕輕地來(lái),輕輕地走占拍,但是不會(huì)導(dǎo)致程序報(bào)錯(cuò)略就。
至此,Runtime的消息機(jī)制就全部梳理完畢~~
Runtime系列文章
Runtime原理探究(一)—— isa的深入體會(huì)(蘋(píng)果對(duì)isa的優(yōu)化)
Runtime原理探究(二)—— Class結(jié)構(gòu)的深入分析
Runtime原理探究(三)—— OC Class的方法緩存cache_t
Runtime原理探究(四)—— 刨根問(wèn)底消息機(jī)制
Runtime原理探究(五)—— super的本質(zhì)
[Runtime原理探究(六)—— Runtime的應(yīng)用...待續(xù)]-()
[Runtime原理探究(七)—— Runtime的API...待續(xù)]-()
Runtime原理探究(八)—— 面試題中的Runtime