消息轉(zhuǎn)發(fā)三部曲:
接上面消息發(fā)送辽旋,如果當(dāng)前類和父類中都沒有找到實(shí)現(xiàn),那么就會(huì)開始嘗試動(dòng)態(tài)方法解析。
動(dòng)態(tài)方法解析
//6.IMP沒有找到墓贿,嘗試方法解析一次
// No implementation found. Try method resolver once.
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;
}
在執(zhí)行了 _class_resolveMethod:
之后储笑,會(huì)跳轉(zhuǎn)到 retry
標(biāo)簽甜熔,重新執(zhí)行查找方法實(shí)現(xiàn)的流程,只不過不會(huì)再調(diào)用 _class_resolveMethod:
方法了突倍,因?yàn)橥ㄟ^ triedResolver
來判斷是否進(jìn)行該類是否進(jìn)行過動(dòng)態(tài)方法解析腔稀。如果首次走到這里,triedResolver = NO
羽历,當(dāng)動(dòng)態(tài)方法解析進(jìn)行過一次之后焊虏,會(huì)設(shè)置 triedResolver = YES
,這樣下次走到這里的時(shí)候秕磷,就不會(huì)再次進(jìn)行動(dòng)態(tài)方法解析诵闭。
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);
}
}
}
_class_resolveMethod
這個(gè)函數(shù)首先判斷是否是 meta-class
類,如果不是元類澎嚣,就執(zhí)行 _class_resolveInstanceMethod
疏尿,如果是元類,執(zhí)行 _class_resolveClassMethod
易桃。
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
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));
}
}
}
static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
assert(cls->isMetaClass());
if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(_class_getNonMetaClass(cls, inst),
SEL_resolveClassMethod, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveClassMethod adds to self->ISA() 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 resolveClassMethod:%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));
}
}
}
在 _class_resolveInstanceMethod
和 _class_resolveClassMethod
方法中褥琐,來查詢是否已經(jīng)在運(yùn)行時(shí)將其動(dòng)態(tài)插入類中的實(shí)現(xiàn)函數(shù),如果沒有重新調(diào)用 lookUpImpOrNil
并重新啟動(dòng)緩存晤郑,來判斷是否已經(jīng)添加上 sel
對(duì)應(yīng)的 IMP
指針敌呈,并且重新觸發(fā) objc_msgSend
方法。
id objc_msgSend(id self, SEL _cmd, ...) {
Class class = object_getClass(self);
IMP imp = class_getMethodImplementation(class, _cmd);
return imp ? imp(self, _cmd, ...) : 0;
}
只要一提 objc_msgSend
造寝,都會(huì)說它的偽代碼如下或類似的邏輯磕洪,反正就是獲取 IMP
并調(diào)用。
IMP class_getMethodImplementation(Class cls, SEL sel)
{
IMP imp;
if (!cls || !sel) return nil;
imp = lookUpImpOrNil(cls, sel, nil, YES/*initialize*/, YES/*cache*/, YES/*resolver*/);
// Translate forwarding function to C-callable external version
if (!imp) {
return _objc_msgForward;
}
return imp;
}
lookUpImpOrNil
函數(shù)獲取不到 IMP
時(shí)就返回 _objc_msgForward
诫龙。
IMP lookUpImpOrNil(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
if (imp == _objc_msgForward_impcache) return nil;
else return imp;
}
lookUpImpOrNil
方法判斷返回 imp
結(jié)果是否和 _objc_msgForward_impcache
相同析显,如果相同返回 nil
,反之返回 imp
签赃。
回到 lookUpImpOrForward
方法中叫榕,如果也沒有找到 imp
的實(shí)現(xiàn)浑侥,那么method resolver
也沒用了,只能進(jìn)入消息轉(zhuǎn)發(fā)階段晰绎。進(jìn)入這個(gè)階段之前寓落,imp
變成 _objc_msgForward_impcache
,最后再加入緩存中荞下。
當(dāng)一個(gè)方法沒有實(shí)現(xiàn)時(shí)伶选,可以通過重寫 resolveInstanceMethod:
和 resolveClassMethod:
方法,動(dòng)態(tài)添加未實(shí)現(xiàn)的方法尖昏。其中第一個(gè)是添加實(shí)例方法仰税,第二個(gè)是添加類方法。這兩個(gè)方法都有一個(gè) BOOL
返回值抽诉,返回 NO
則進(jìn)入消息轉(zhuǎn)發(fā)機(jī)制陨簇。
void dynamicMethodIMP(id self, SEL _cmd) {
// implementation ....
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(resolveThisMethodDynamically)) {
class_addMethod([self class], sel, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
objc_msgForward
STATIC_ENTRY __objc_msgForward_impcache
// Method cache version
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band condition register is NE for stret, EQ otherwise.
MESSENGER_START
nop
MESSENGER_END_SLOW
jne __objc_msgForward_stret
//1.跳轉(zhuǎn)到__objc_msgForward
jmp __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
// Non-stret version
//2.執(zhí)行__objc_forward_handler
movq __objc_forward_handler(%rip), %r11
jmp *%r11
END_ENTRY __objc_msgForward
ENTRY __objc_msgForward_stret
// Struct-return version
movq __objc_forward_stret_handler(%rip), %r11
jmp *%r11
END_ENTRY __objc_msgForward_stret
在執(zhí)行 _objc_msgForward
之后會(huì)調(diào)用 __objc_forward_handler
函數(shù)。
__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;
看源碼實(shí)現(xiàn)當(dāng)我們給一個(gè)對(duì)象發(fā)送一個(gè)沒有實(shí)現(xiàn)的方法的時(shí)候迹淌,如果其父類也沒有這個(gè)方法河绽,則會(huì)崩潰,報(bào)錯(cuò)信息類似于這樣:unrecognized selector sent to instance
唉窃,然后接著會(huì)跳出一些堆棧信息耙饰,這些信息就是從這里而來。
重定向
- (id)forwardingTargetForSelector:(SEL)aSelector
當(dāng)動(dòng)態(tài)方法解析不作處理返回 NO
時(shí)纹份,消息轉(zhuǎn)發(fā)機(jī)制會(huì)被觸發(fā)苟跪。在這時(shí)forwardInvocation:
方法會(huì)被執(zhí)行。
在消息轉(zhuǎn)發(fā)機(jī)制執(zhí)行前蔓涧,Runtime
系統(tǒng)會(huì)再給我們一次偷梁換柱的機(jī)會(huì)件已,即通過重載 - (id)forwardingTargetForSelector:(SEL)aSelector
方法替換消息的接受者為其他對(duì)象:
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if(aSelector == @selector(mysteriousMethod:)){
return alternateObject;
}
return [super forwardingTargetForSelector:aSelector];
}
轉(zhuǎn)發(fā)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
當(dāng) forwardingTargetForSelector:
方法未做出任何響應(yīng)的話,會(huì)來到消息轉(zhuǎn)發(fā)流程元暴。消息轉(zhuǎn)發(fā)時(shí)會(huì)首先調(diào)用 methodSignatureForSelector:
方法篷扩,在方法內(nèi)部生成 NSMethodSignature
類型的方法簽名對(duì)象。在生成簽名對(duì)象時(shí)昨寞,可以指定 target
和 SEL
瞻惋,可以將這兩個(gè)參數(shù)換成其他參數(shù)厦滤,將消息轉(zhuǎn)發(fā)給其他對(duì)象援岩。
[otherObject methodSignatureForSelector:otherSelector];
生成 NSMethodSignature
簽名對(duì)象后,就會(huì)調(diào)用 forwardInvocation:
方法掏导,這是消息轉(zhuǎn)發(fā)中最后一步了享怀,如果在這步還沒有對(duì)消息進(jìn)行處理,則會(huì)導(dǎo)致崩潰趟咆。
該消息的唯一參數(shù)是個(gè) NSInvocation
類型的對(duì)象添瓷,該對(duì)象封裝了原始的消息和消息的參數(shù)梅屉。我們可以實(shí)現(xiàn) forwardInvocation:
方法來對(duì)不能處理的消息做一些默認(rèn)的處理,也可以將消息轉(zhuǎn)發(fā)給其他對(duì)象來處理鳞贷,而不拋出錯(cuò)誤坯汤。
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if ([object respondsToSelector:[anInvocation selector]]) {
[anInvocation invokeWithTarget:object];
} else {
[super forwardInvocation:anInvocation];
}
}
這里需要注意的是參數(shù) anInvocation
是從哪的來的呢?其實(shí)在 forwardInvocation:
消息發(fā)送前搀愧,Runtime
系統(tǒng)會(huì)向?qū)ο蟀l(fā)送 methodSignatureForSelector:
消息惰聂,并取到返回的方法簽名用于生成 NSInvocation
對(duì)象。所以我們?cè)谥貙?forwardInvocation:
的同時(shí)也要重寫 methodSignatureForSelector:
方法咱筛,否則會(huì)拋異常搓幌。
當(dāng)一個(gè)對(duì)象由于沒有相應(yīng)的方法實(shí)現(xiàn)而無(wú)法響應(yīng)某消息時(shí),運(yùn)行時(shí)系統(tǒng)將通過 forwardInvocation:
消息通知該對(duì)象迅箩。每個(gè)對(duì)象都從 NSObject
類中繼承了 forwardInvocation:
方法溉愁。然而,NSObject
中的方法實(shí)現(xiàn)只是簡(jiǎn)單地調(diào)用了 doesNotRecognizeSelector:
饲趋。通過實(shí)現(xiàn)我們自己的 forwardInvocation:
方法拐揭,我們可以在該方法實(shí)現(xiàn)中將消息轉(zhuǎn)發(fā)給其它對(duì)象。
forwardInvocation:
方法就像一個(gè)不能識(shí)別的消息的分發(fā)中心篙贸,將這些消息轉(zhuǎn)發(fā)給不同接收對(duì)象投队。或者它也可以象一個(gè)運(yùn)輸站將所有的消息都發(fā)送給同一個(gè)接收對(duì)象爵川。它可以將一個(gè)消息翻譯成另外一個(gè)消息敷鸦,或者簡(jiǎn)單的”吃掉“某些消息,因此沒有響應(yīng)也沒有錯(cuò)誤寝贡。
forwardInvocation:
方法也可以對(duì)不同的消息提供同樣的響應(yīng)扒披,這一切都取決于方法的具體實(shí)現(xiàn)。該方法所提供是將不同的對(duì)象鏈接到消息鏈的能力圃泡。
注意:
forwardInvocation:
方法只有在消息接收對(duì)象中無(wú)法正常響應(yīng)消息時(shí)才會(huì)被調(diào)用碟案。 所以,如果我們希望一個(gè)對(duì)象將 negotiate
消息轉(zhuǎn)發(fā)給其它對(duì)象颇蜡,則這個(gè)對(duì)象不能有 negotiate
方法价说。否則,forwardInvocation:
將不可能會(huì)被調(diào)用风秤。
實(shí)戰(zhàn)
1鳖目、動(dòng)態(tài)解析
我們?cè)?code>Car類的.m
文件里面,通過上面介紹動(dòng)態(tài)解析可以知道缤弦,可以重載resolveInstanceMethod:
和resolveClassMethod:
方法分別添加實(shí)例方法實(shí)現(xiàn)和類方法實(shí)現(xiàn)领迈。因?yàn)楫?dāng)Runtime
系統(tǒng)在Cache
和方法分發(fā)表中找不到要執(zhí)行的方法時(shí),Runtime
會(huì)調(diào)用resolveInstanceMethod:
或resolveClassMethod:
來給程序員一次動(dòng)態(tài)添加方法實(shí)現(xiàn)的機(jī)會(huì)。
2狸捅、重定向
我們新建一個(gè)Person
類衷蜓,為了讓運(yùn)行時(shí)系統(tǒng)能夠運(yùn)行到forwardingTargetForSelector:
方法,我們先在resolveInstanceMethod:
中返回NO
尘喝,代碼如下:
從運(yùn)行結(jié)果中看出磁浇,我們執(zhí)行[person fly]
方法,控制臺(tái)中打出Car
的run
方法朽褪,最終也實(shí)現(xiàn)了消息的轉(zhuǎn)發(fā)扯夭。
Person *person = [[Person alloc] init];
[person fly];
3、轉(zhuǎn)發(fā)
如果我們都不實(shí)現(xiàn)forwardingTargetForSelector
鞍匾,系統(tǒng)就會(huì)方法methodSignatureForSelector
和forwardInvocation
來實(shí)現(xiàn)轉(zhuǎn)發(fā)交洗,代碼如下:
從運(yùn)行結(jié)果中看出,我們執(zhí)行[person fly]
方法橡淑,控制臺(tái)中打出Car
的run
方法构拳,最終也實(shí)現(xiàn)了消息的轉(zhuǎn)發(fā)。
注意:
-
methodSignatureForSelector
用來生成方法簽名梁棠,這個(gè)簽名就是給forwardInvocation
中的參數(shù)NSInvocation
調(diào)用的置森。 -
unrecognized selector sent to instance
,原來就是因?yàn)?code>methodSignatureForSelector這個(gè)方法中符糊,由于沒有找到fly
對(duì)應(yīng)的實(shí)現(xiàn)方法凫海,所以返回了一個(gè)空的方法簽名,最終導(dǎo)致程序報(bào)錯(cuò)崩潰男娄。
以上就是消息的轉(zhuǎn)發(fā)行贪,如果有覺得上述我講的不對(duì)的地方歡迎指出,大家多多交流溝通模闲。