在iOS-慢速方法查找和iOS-快速方法查找中我们分别提到了objc_msgSend
的快速查找和慢速查找,如果经历这两步仍未找到该方法的imp
会怎么样呢?
Apple给了我们两次补救的机会,
动态方法决议:慢速查找流程未找到后,会执行一次动态方法决议
消息转发:如果动态方法决议仍然没有找到实现,则进行消息转发
如果这两次机会都没有做任何操作,就会报我们日常开发中常见的方法未实现的崩溃报错——unrecognized selector sent to
。
我们来看看是怎么从慢速查找进入到第一个机会——动态方法决议的吧。
康康该方法的源码
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 (!lookUpImpOrNil(inst, sel, cls)) { //如果没有找到或者为空,在元类的对象方法解析方法中查找
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
//如果方法解析中将其实现指向其他方法,则继续走方法查找流程
return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
先看看对象方法的动态决议源码
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))) {//查找resolveInstanceMethod是否实现,其实源码中NSObject实现了该方法。
// Resolver not implemented.
return;
}
//消息转发,执行resolveInstanceMethod方法
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) {
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));
}
}
}
我们来代码验证一下,给LGPerson.h
添加如下代码
@interface LGPerson : NSObject
+ (void)say1;//只声明,未实现
+ (void)say4;
- (void)say2;
- (void)say3;//只声明,未实现
@end
给LGPerson.m
添加如下代码
@implementation LGPerson
//- (void)say1
//{
// NSLog(@"%s",__func__);
//}
- (void)say2
{
NSLog(@"%s",__func__);
}
//- (void)say3
//{
// NSLog(@"%s",__func__);
//}
+ (void)say4
{
NSLog(@"%s",__func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(say3)) {
NSLog(@"%@ 来了", NSStringFromSelector(sel));
}
return [super resolveInstanceMethod:sel];
}
@end
在 main
中调用say3
,执行结果如下:
可以看到
resolveInstanceMethod
执行了两次,why?-第一次的“来了”是在查找say3方法时会进入动态方法决议
-第二次“来了”是在慢速转发流程中调用了CoreFoundation框架中的NSObject(NSObject) methodSignatureForSelector:后,会再次进入动态决议
可通过
lldb
的bt
命令看看堆栈信息验证如果想抓住这个防止崩溃的机会,可以修改resolveInstanceMethod
代码如下
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(say3)) {
NSLog(@"%@ 来了", NSStringFromSelector(sel));
//获取say2方法的imp
IMP imp = class_getMethodImplementation(self, @selector(say2));
//获取say2的实例方法
Method sayMethod = class_getInstanceMethod(self, @selector(say2));
//获取say2的签名
const char *type = method_getTypeEncoding(sayMethod);
//将sel的实现指向say2
return class_addMethod(self, sel, imp, type);
}
//
return [super resolveInstanceMethod:sel];
}