(一)动态方法决议
- 案例如下 创建一个LGTeacher类,声明一个say666方法,方法不实现
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGTeacher *p = [LGTeacher alloc];
[LGTeacher say666];
}
return 0;
}
unrecognized selector 经典的方法为实现奔溃,我们没有写log 为什么会有这样的输出呢?
-
在方法慢速查找我们找到相关代码
-
这里找到给forward_imp 一个定语赋值,当慢速查找父类为nil时
当慢速查找没有找到时会进入_bjc_msgForward_impcache 方法决议
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
- 从源码中找到x17,进入 __objc_forward_handler 方法判断
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;
从_objc_forward_handler 方法中终于找到日志实现的地方
思考:????当返回unrecognized selector 之前 lookUpImpOrForward 里面有没做其他操作?
在探究慢速查找流程lookUpImpOrForward中,如果没有查找到imp就会走动态方法决议流程resolveMethod_locked
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior){
...
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
- 从上面判断逻辑可以得出resolveMethod_locked 只会进入一次 我们看下resolveMethod_locked 里面做了什么??
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
// 给你一次机会 拯救地球 -> imp
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
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior); 从这句代码可以得出resolveMethod_locked是做了一次容错,从新给一次拯救机会
resolveInstanceMethod 是非元类实现的方法(实例方法),resolveClassMethod元类方法(类方法)
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(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);
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) {
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));
}
}
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, sel);BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);这2个方法都调用 objc_msgSend 向系统发送消息
在LGTeacher类中添加resolveInstanceMethod方法
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"resolveInstanceMethod :%@-%@",self,NSStringFromSelector(sel));
return [super resolveInstanceMethod:sel];
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGTeacher *p = [LGTeacher alloc];
[p say666];
}
return 0;
}
- lldb输出为
2021-07-12 17:37:18.775526+0800 KCObjcBuild[7443:173330] resolveInstanceMethod :LGTeacher-say666
2021-07-12 17:37:18.776380+0800 KCObjcBuild[7443:173330] resolveInstanceMethod :LGTeacher-say666
2021-07-12 17:37:18.776749+0800 KCObjcBuild[7443:173330] -[LGTeacher say666]: unrecognized selector sent to instance 0x1011405d0
- 从输出可以看出resolveInstanceMethod调用了2次 为什么呢
-
从图看出第一调用发起者是resolveMethod_locked 可以看到走的是慢速查找流程的动态决议方法
上图可以看出第二次 发起者CoreFoundation`-[NSObject(NSObject) methodSignatureForSelector:]:
根据上面分析得出动态消息决议流程图