一、forward_imp
在上一节,我们讲到了在寻找imp
的递归父类过程中,如果父类为nil
,那么会执行imp = forward_imp
继续寻找。下面我们通过一个例子来学习:
通常我们在只写方法声明,不写方法实现或者利用函数performSelector:@selector(XXX)
但是都没有实现方法的时候,就会报方法找不到错误:unrecognized selector sent to instance XXX
。其实这个错误来自于lookUpImpOrForward
中的forward_imp
方法:
const IMP forward_imp = (IMP)_objc_msgForward_impcache; // 汇编方法
全局搜索objc_msgForward_impcache
,可以发现这个方法在汇编里面:
/********************************************************************
*
* id _objc_msgForward(id self, SEL _cmd,...);
*
* _objc_msgForward is the externally-callable
* function returned by things like method_getImplementation().
* _objc_msgForward_impcache is the function pointer actually stored in
* method caches.
*
********************************************************************/
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward // 进入 __objc_msgForward
adrp x17, __objc_forward_handler@PAGE // 调用__objc_forward_handler方法
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17 // 返回函数指针
END_ENTRY __objc_msgForward // 结束 __objc_msgForward
__objc_msgForward_impcache
里面调用__objc_msgForward
方法进入__objc_msgForward
,我们先介绍下ADRP指令
:
- 编译时,首先会计算出当前PC到exper的偏移量#offset_to_exper
- pc的低12位清零,然后加上偏移量,给register
- 得到的地址,是含有label的4KB对齐内存区域的base地址
Page 的地址:符号扩展 21 位偏移量,将其向左移 12,并将其添加到 PC 的值中,并清除其底部 12 位,
将结果写入寄存器 Xd。这计算包含标签的4KiB对齐内存区域的基址,并设计为与提供标签地址底部12位的加载,
存储或ADD指令结合使用。这允许使用两条指令对PC的+4GiB内的任何位置进行位置依赖寻址,
前提是动态重新定位的最小粒度为4KiB(即标签地址的底部12位不受重新定位的影响)。
术语“页面”是4KiB重定位颗粒的简写,不一定与虚拟内存页面大小有关。
通俗讲为:ADRP指令可以理解成先进行PC + imm(偏移值)然后找到lable
所在的一个4KB的页,然后取得label
的基址,再进行偏移去寻址。
这里就是 adrp
指令计算出这个__objc_forward_handler@PAGE
(偏移量),之后将基址存到寄存器X17中,关键就是__objc_forward_handler
方法。
ldr: LDR R0,[R1, #8];
将存储器地址为R1+8的字数据读入寄存器R0。这里实际上将x17又去除偏移量, 给到p17中。
可看到其实关键是调用了一个__objc_forward_handler
,我们全局搜索一下可发现是一个C++方法,在objc-runtime.mm
中, 看下_objc_forward_handler
:
/***********************************************************************
* objc_setForwardHandler
**********************************************************************/
#if !__OBJC2__
// Default forward handler (nil) goes to forward:: dispatch.
void *_objc_forward_handler = nil;
void *_objc_forward_stret_handler = nil;
#else
// Default forward handler halts the process.
__attribute__((noreturn, cold)) 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;
可看到报错信息...unrecognized selector sent to instance XXX...
就是我们经典的报错方法, 并且其中可看到底层是不区分类方法 +与对象方法/实例方法 -的。“+”/ "-"在这里是人为拼接上的。我们之前在Clang也看过类方法在是元类调用实例方法形式存在的。调用地方需要返回再看lookUpImpOrForward
方法,如果父类链都没找到就令imp = forward_imp;
最终方法如果没有找到就会往下走return imp
即return forward_imp
,就会报那个经典方法找不到错误。
二、动态方法决议
仔细查看lookUpImpOrForward
源码,imp = forward_imp;
赋值后只是单纯break
出了循环,并不会直接return
。在return
前(循环外面)可以看到一个if
条件块:
// No implementation found. Try method resolver once.
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
这里就是进行动态方法决议的部分。据此,我们可以画出lookUpImpOrForward
慢速查找流程图:
看了流程图,相信大家都对这个流程又了一定的认识。下面就先看一下resolveMethod_locked
的代码:
/***********************************************************************
* resolveMethod_locked
* Call +resolveClassMethod or +resolveInstanceMethod.
*
* Called with the runtimeLock held to avoid pressure in the caller
* Tail calls into lookUpImpOrForward, also to avoid pressure in the callerb
**********************************************************************/
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
if (! cls->isMetaClass()) {
// 当前类不是元类, 即查找的是实例方法
// try [cls resolveInstanceMethod:sel]
// 实例方法的动态方法决议流程
resolveInstanceMethod(inst, sel, cls);
}
else {
// 当前类是元类, 即查找的是类方法
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
// 类方法的动态方法决议流程
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
// 如果有动态方法决议, 处理了, 就调用lookUpImpOrForwardTryCache重新帮你处理下
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
可以这样理解,当前方法都imp
没找到,系统给一次补救机会:动态方法决议。
- 实例方法 -> resolveInstanceMethod
- 类方法 -> resolveClassMethod
下面先看实例方法的。
(一)实例方法:resolveInstanceMethod
/***********************************************************************
* resolveInstanceMethod
* Call +resolveInstanceMethod, looking for a method to be added to class cls.
* cls may be a metaclass or a non-meta class.
* Does not check if the method already exists.
**********************************************************************/
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
// 定义一个resolveInstanceMethod 的方法编号resolve_sel
SEL resolve_sel = @selector(resolveInstanceMethod:);
// 判断当前类, 父类等(NSObject默认有决议方法) 是否有决议方法resolveInstanceMethod,
// 根本没写就直接返回
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, 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 = lookUpImpOrNilTryCache(inst, sel, cls);
if (resolved && PrintResolving) {
...
}
}
动态方法决议其实是调用了一个决议方法,拿实例方法举例就是resolveInstanceMethod
,如果当前imp
没有, 会去父类链查找resolveInstanceMethod
(NSObject
默认有),若是这个方法里面处理了我们查找的sel
、实现、转发等等,那么系统也算你成功并不会报错。
lookUpImpOrNilTryCache
// 定义一个resolveInstanceMethod 的方法编号resolve_sel
SEL resolve_sel = @selector(resolveInstanceMethod:);
// 判断当前类, 父类等(NSObject默认有决议方法) 是否有决议方法resolveInstanceMethod,
// 根本没写就直接返回
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
// Resolver not implemented.
return;
}
定义一个决议方法SEL:resolve_sel
,然后走下面if
判断,是否存在。lookUpImpOrNilTryCache
的第三个参数是:cls->ISA(true)
inline Class
objc_object::ISA(bool authenticated)
{
ASSERT(!isTaggedPointer());
return isa.getDecodedClass(authenticated);
}
inline Class
isa_t::getDecodedClass(bool authenticated) {
#if SUPPORT_INDEXED_ISA//真机为1, 否则为0
// 判断是否为nonpointer, 通常已经初始化完毕的类, isa中nonpointer为nil
if (nonpointer) {
return classForIndex(indexcls);
}
// 返回当前类
return (Class)cls;
#else
// 非真机getClass( )
return getClass(authenticated);
#endif
}
回到lookUpImpOrNilTryCache
:
IMP lookUpImpOrNilTryCache(id inst, SEL sel, Class cls, int behavior)
{
return _lookUpImpTryCache(inst, sel, cls, behavior | LOOKUP_NIL);
}
可发现它是调用_lookUpImpTryCache
方法。
补救
我们看一下补救的措施,在main
里:
[me performSelector:@selector(sayHello2)];
实际上没有做sayHello2
的声明和实现,但我们实现类方法resolveInstanceMethod:
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(sayHello2)) {
//获取sayHello方法的imp
IMP imp = class_getMethodImplementation(self, @selector(sayHello));
//获取sayHello的实例方法
Method sayMethod = class_getInstanceMethod(self, @selector(sayHello));
//获取sayMethod的方法签名
const char *type = method_getTypeEncoding(sayMethod);
//将sel的实现指向
return class_addMethod(self, sel, imp, type);
return YES;
}
return [super resolveInstanceMethod:sel];
}
在执行实例方法sayHello2
时,在快速、慢速查找都没有找到的情况下,就会执行到resolveInstanceMethod
方法。我们可以看到,它正常输出了。
(二)类方法:resolveClassMethod
/***********************************************************************
* resolveClassMethod
* Call +resolveClassMethod, looking for a method to be added to class cls.
* cls should be a metaclass.
* Does not check if the method already exists.
**********************************************************************/
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
// Resolver not implemented.
return;
}
Class nonmeta;
{
mutex_locker_t lock(runtimeLock);
nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
// +initialize path should have realized nonmeta already
if (!nonmeta->isRealized()) {
_objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
nonmeta->nameForLogging(), nonmeta);
}
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(nonmeta, @selector(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 = lookUpImpOrNilTryCache(inst, sel, cls);
if (resolved && PrintResolving) {
...
}
}
resolveClassMethod
中cls
本来是元类,通过getMaybeUnrealizedNonMetaClass
来获取元类对应的类对象,然后再对类对象发送消息(相当于调用类方法)。
补救:
[me sayNB];
我们调用一个没有实现的类方法,在类的实现里面实现resolveClassMethod
方法:
+ (BOOL)resolveClassMethod:(SEL)sel{
if (sel == @selector(sayNB)) {
IMP imp = class_getMethodImplementation(objc_getMetaClass("MyPerson"), @selector(sayFuckOff));
Method classMethod = class_getInstanceMethod(objc_getMetaClass("MyPerson"), @selector(sayFuckOff));
const char *type = method_getTypeEncoding(classMethod);
return class_addMethod(objc_getMetaClass("MyPerson"), sel, imp, type);
}
return [super resolveClassMethod:sel];
}
就可以完美补救了。
三、消息转发
如果动态解析(动态决议)阶段还是没有对应的方法,那么就会来到消息转发阶段。消息转发阶段分为两部分,替换消息接收者阶段(快速转发),和完全消息转发阶段(慢速转发)。
(一)快速转发流程
实现forwardingTargetForSelector
方法,并返回替换的对象。先command + shift + 0
,来打开开发文档进行查阅:
翻译一下Discussion部分:
如果对象实现(或继承)此方法,并返回非 nil
(和非 self
)结果,则返回的对象将用作新的接收方对象,并且消息调度将恢复到该新对象。(显然,如果你从这种方法返回self
,代码就会陷入无限循环。)
如果在非 root
类中实现此方法,并且对于给定的选择器,您的类没有任何可返回的内容,则应返回调用 super
的实现的结果。
此方法使对象有机会在更昂贵的 forward
调用之前重定向发送到它的未知消息:机器接管。当您只想将消息重定向到另一个对象时,这很有用,并且可能比常规转发快一个数量级。如果转发的目标是捕获 NSinvocation
,或者在转发过程中操作参数或返回值,则此方法没有用处。
能够得知这个方法是一个重定向的过程。我们可以通过实现MyPerson
的forwardingTargetForSelector:
方法来使它成功调用它本身并未实现的方法sayBBB
:
//第二根稻草,使用快速消息转发,找其他对象来实现方法
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(sayBBB)) {
return [PersonB alloc];
}
return nil;
}
这样就完成了消息转发,而且不像动态方法决议那样臃肿。以上就是快速转发的流程了。
(二)慢速转发流程
若是PersonB
类并无实现sayBBB
方法呢?那么就会进入到methodSignatureForSelector
方法,即慢速转发流程。还是先看一下开发文档:
翻译下Discussion部分:
此方法用于协议的实现。此方法还用于必须创建 NSinvocation
对象的情况,例如在消息转发期间。如果对象维护委托或能够处理它不直接实现的消息,则应重写此方法以返回适当的方法签名。
再看Return Value部分,可以知道这是一个返回方法签名的过程。
那么我们在类中先重写methodSignatureForSelector
方法,根据开发文档可知这个方法须要搭配NSInvocation
,以及返回适当的方法签名,即NSMethodSignature
。
//第三根稻草,使用完全消息转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(sayBBB)) {
// NSMethodSignature *sig = [NSMethodSignature methodSignatureForSelector:aSelector];
NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:"v@:"];
return sig;
}
return nil;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL aSelector = [anInvocation selector];
if (aSelector == @selector(sayBBB)) {
anInvocation.selector = @selector(sayAAA);
[anInvocation invokeWithTarget:[PersonB alloc]];
} else {
[super forwardInvocation:anInvocation];
}
}
四、查看消息转发流程
虽然我们对消息转发流程以及有所了解了,可是咱们并无在源码中看到调用的过程,那么究竟是怎么被调用的呢?在崩溃界面使用bt
指令查看堆栈。
我们在Xcode的包里面可以找到CoreFoundation的可执行文件,我们下载工具“Hopper”的试用版帮助我们反汇编。搜索___forwarding_prep_0___
可以看到:
int ___forwarding_prep_0___(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7) {
r29 = &saved_fp;
var_10 = q7;
var_20 = q6;
var_30 = q5;
var_40 = q4;
var_50 = q3;
var_60 = q2;
var_70 = q1;
var_80 = q0;
var_90 = r8;
var_98 = arg7;
var_A0 = arg6;
var_A8 = arg5;
var_B0 = arg4;
var_B8 = arg3;
var_C0 = arg2;
var_C8 = arg1;
r0 = ____forwarding___(&var_D0, 0x0);
if (r0 != 0x0) {
r0 = *r0;
}
else {
r0 = objc_msgSend(var_D0, var_C8);
}
return r0;
}
双击进入____forwarding___
,下面就保留主要代码了:
void ____forwarding___(int arg0, int arg1) {
r29 = &saved_fp;
r31 = r31 + 0xffffffffffffffa0 - 0x110;
r19 = &var_160;
r25 = arg1;
r20 = arg0;
r21 = *(int128_t *)arg0;
r22 = *(int128_t *)(arg0 + 0x8);
if ((r21 & 0xffffffff80000000) == 0x0) goto loc_119f20;
...
loc_119f20:
r0 = object_getClass(r21);
r23 = r0;
r24 = class_getName(r0);
if (class_respondsToSelector(r23, @selector(forwardingTargetForSelector:)) == 0x0) goto loc_119f68;//⚠️
// 先判断是否有forwardingTargetForSelector方法。
// 若是forwardingTargetForSelector方法存在,即进入快速转发流程,
// 调用forwardingTargetForSelector方法
// 否则goto loc_119f68
loc_119f4c:
r0 = [r21 forwardingTargetForSelector:r2];
if (r0 != 0x0) {
asm {
ccmp x0, x21, #0x4, ne };
}
if (CPU_FLAGS & NE) goto loc_11a200;
loc_119f68://⚠️
if (strncmp(r24, "_NSZombie_", 0xa) == 0x0) goto loc_11a2c0;
// 判断是不是僵尸对象,不是则继续
loc_119f80:
if ((class_respondsToSelector(r23, @selector(methodSignatureForSelector:)) & 0x1) == 0x0) goto loc_11a32c;//⚠️⚠️⚠️
// 如果methodSignatureForSelector:实现了继续往下走到forwardInvocation:
// 如果methodSignatureForSelector:没找到就执行了doesNotRecognizeSelector:
loc_119f98:
r0 = [r21 methodSignatureForSelector:r2];
if (r0 == 0x0) goto loc_11a398;
...
loc_11a0ec:
if (class_respondsToSelector(object_getClass(r21), @selector(forwardInvocation:)) == 0x0) goto loc_11a374;
loc_11a108:
r25 = [NSInvocation _invocationWithMethodSignature:r23 frame:r20];
[r21 forwardInvocation:r2];
r26 = 0x0;
goto loc_11a13c;
loc_11a374:
____forwarding___.cold.4(r19 + 0x0, r21);
return;
loc_11a398://⚠️⚠️
r0 = sel_getName(r22);
r20 = r0;
r0 = sel_getUid(r0);
if (r0 != r22) {
_CFLog(0x4, @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort");
}
if (class_respondsToSelector(object_getClass(r21), @selector(doesNotRecognizeSelector:)) != 0x0) {
r1 = @selector(doesNotRecognizeSelector:);
r0 = objc_msgSend(r21, r1);
asm {
brk #0x1 };
loc_11a400(r0, r1);
}
else {
____forwarding___.cold.3(r21);
}
return;
loc_11a32c://⚠️⚠️⚠️
r23 = class_getSuperclass(r23);
r20 = object_getClassName(r21);
if (r23 == 0x0) {
object_getClassName(r21);
_CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- did you forget to declare the superclass of '%s'?");
r31 = (r31 - 0x20) + 0x20;
}
else {
_CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- trouble ahead");
r31 = r31 + 0x0;
}
goto loc_11a398;//⚠️⚠️
loc_11a2c0:
____forwarding___.cold.2(r21, r24, r22);
return;
...
全部的没有找到相对应的方法最终都会执行doesNotRecognizeSelector
方法。不管是_forwardStackInvocation:
方法(上面省略了)还是forwardInvocation:
方法,都能goto loc_11a13c
:
loc_11a13c:
if (*(int8_t *)(r25 + 0x34) != 0x0) {
r8 = *r24;
if ((*(int8_t *)(r8 + 0x22) & 0x80) != 0x0) {
r9 = *(r25 + 0x8);
r10 = *(int32_t *)(r8 + 0x1c);
r12 = *(int8_t *)(r8 + 0x20);
memmove(*(r12 + r20 + r10), *(r12 + r9 + r10), *(int32_t *)(*r8 + 0x10));
}
}
r0 = [r23 methodReturnType];
r8 = *(int8_t *)r0;
if (r8 != 0x76 && (r8 != 0x56 || *(int8_t *)(r0 + 0x1) != 0x76)) {
r20 = *(r25 + 0x10);
if (r22 != 0x0) {
r20 = [[NSData dataWithBytes:r20 length:r26] bytes];
[r25 release];
}
}
else {
if (r22 != 0x0) {
[r25 release];
}
r20 = 0x694620;
}
goto loc_11a26c;
loc_11a26c:
if (**___stack_chk_guard != **___stack_chk_guard) {
__stack_chk_fail();
}
return;
应该就是直接把结果返回了。在反汇编流程中能够看到系统调用了一个_forwardStackInvocation
方法,这个方法并无对外暴露,在forwardInvocation:
方法前面被判断调用,我们试着重写一下看看效果:
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"%s %@", __func__, NSStringFromSelector(anInvocation.selector));
}
- (void)_forwardStackInvocation:(NSInvocation *)anInvocation {
NSLog(@"%s %@", __func__, NSStringFromSelector(anInvocation.selector));
}
输出:
-[MyPerson _forwardStackInvocation:] sayBBB
确实,有_forwardStackInvocation
方法的时候就不会再走forwardInvocation:
方法了。
动态决议方法为什么走两次
细心的小伙伴可能已经发现了,在resolveInstanceMethod:
打断点,发现它走两遍。我们在objc
源码的对象动态方法决议(resolveInstanceMethod
)里面打上断点,bt查看堆栈:
发现第二次进来是由于在CoreFoundation
库中的methodSignatureForSelector
方法里的__methodDescriptionForSelector
方法调用了objc
库中的class_getInstanceMethod
方法。
真正的methodSignatureForSelector
源码是在CoreFoundation
库中,去反汇编中进行查看:
int -[NSObject methodSignatureForSelector:]() {
var_10 = r20;
stack[-24] = r19;
r31 = r31 + 0xffffffffffffffe0;
saved_fp = r29;
stack[-8] = r30;
if (r2 != 0x0) {
r0 = objc_opt_class();
r1 = r2;
if (___methodDescriptionForSelector(r0, r1) != 0x0) {
r0 = [NSMethodSignature signatureWithObjCTypes:r1];
}
else {
r0 = 0x0;
}
}
else {
r0 = 0x0;
}
return r0;
}
发现确实有__methodDescriptionForSelector
方法,进一步跟进:
int ___methodDescriptionForSelector(int arg0, int arg1) {
r31 = r31 - 0x60;
var_40 = r26;
stack[-72] = r25;
var_30 = r24;
stack[-56] = r23;
var_20 = r22;
stack[-40] = r21;
var_10 = r20;
stack[-24] = r19;
saved_fp = r29;
stack[-8] = r30;
r19 = arg1;
r20 = arg0;
if (arg0 == 0x0) goto loc_12566c;
...
loc_12566c:
r0 = class_getInstanceMethod(r20, r19);
if (r0 != 0x0) {
r0 = method_getDescription(r0);
r24 = *(int128_t *)r0;
r23 = *(int128_t *)(r0 + 0x8);
}
else {
r23 = 0x0;
r24 = 0x0;
}
goto loc_125690;
...
}
发现确实调用了class_getInstanceMethod
方法,再去objc
源码中进行查看:
/***********************************************************************
* class_getInstanceMethod. Return the instance method for the
* specified class and selector.
**********************************************************************/
Method class_getInstanceMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
// This deliberately avoids +initialize because it historically did so.
// This implementation is a bit weird because it's the only place that
// wants a Method instead of an IMP.
#warning fixme build and search caches
// Search method lists, try method resolver, etc.
lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);
#warning fixme build and search caches
return _class_getMethod(cls, sel);
}
发现的确调用了lookUpImpOrForward
函数,因此动态方法决议会被调用2次。
五、动态方法决议和消息转发总结
在网上看到这张图不错,拿来总结一下吧。
参考博客:
http://www.javashuo.com/article/p-fnmnrbei-ws.html
https://juejin.cn/post/7094940251419836430#heading-5
https://blog.csdn.net/miaocuilin/article/details/118502088
https://blog.csdn.net/weixin_40918107/article/details/108811627