前言
在前面的 objc_msgSend 流程分析(快速查找) 和 objc_msgSend 流程分析(慢速查找) 我们分析了 objc_msgSend
的快速查找以及慢速查找流程,如果这两步都没有找到,会怎么样呢?
首先,我们来看个示例,定义一个 LCPerson
类,声明一个实例方法和一个类方法,实例方法和类方法均没有实现
@interface LCPerson : NSObject
- (void)say666;
+ (void)say888;
@end
@implementation LCPerson
@end
在 main
函数中分别调用实例方法和类方法
- 调用实例方法
say666
- 调用类方法
say888
可以看到,运行程序,都崩溃了,报错也是我们在开发中最常见的方法未实现。
方法未实现报错源码
根据上一篇消息的慢速查找流程,我们发现,方法查找不到,最终会走到 __objc_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
以上主要调用了 __objc_forward_handler
,继续查看 __objc_forward_handler
的实现,并没有找到, 在源码中去掉一个下划线进行全局搜索 _objc_forward_handler
,有如下实现,本质是调用的objc_defaultForwardHandler
方法
// 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;
看到这里,是不是很熟悉,这就是我们在开发中常见的错误:方法没实现
objc_msgSend
是不是走完了快速查找和慢速查找后就直接崩溃了呢?其实并不是,苹果还给了最后的三次机会,动态方法决议
、快速转发
、慢速转发
。
动态方法决议
在 慢速查找
流程未找到方法实现时,首先会尝试一次 动态方法决议
,其源码如下:
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
//如果当前的 Class 不是元类,则调用对象的解析方法
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
resolveInstanceMethod(inst, sel, cls);
}
//如果当前的 Class 是元类,则调用类的解析方法
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);
}
流程图如下
实例方法 resolveInstanceMethod
查看它的源码实现
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
// 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 = lookUpImpOrNil(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));
}
}
}
- 查找
cls
类中是否有该方法的实现,通过lookUpImpOrNil
方法又会进入慢速查找流程查找resolveInstanceMethod
方法- 没有,直接返回
- 有,发送
resolveInstanceMethod
消息
- 通过
lookUpImpOrNil
方法再次进入慢速查找流程查找实例方法
类方法 resolveClassMethod
源码实现如下
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
if (!lookUpImpOrNil(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 = lookUpImpOrNil(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));
}
}
}
可以看到,实现与实例方法类似,只是传入的 cls
不再是 类
,而是 元类
。
示例验证
我们在 LCPerson
类中实现 resolveInstanceMethod
方法,并打印信息验证是否会走这个方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(say666)) {
NSLog(@"%s 来了", __func__);
}
return [super resolveInstanceMethod:sel];
}
运行项目,查看结果
从上面的打印可以发现,在崩溃之前确实调用了 resolveInstanceMethod
方法,但是为什么会调用两次呢?实际上第一次的打印是在查找 say666
方法时会进入 动态方法决议
,第二次打印是在慢速转发流程中调用了 CoreFoundation
框架中的 methodSignatureForSelector
后,会再次进入动态决议,这个在文末再详解。
崩溃解决
知道了动态方法决议,我们就可以通过在类中重写 resolveInstanceMethod
类方法,并将其指向其他方法的实现,即在 LGPerson
中重写resolveInstanceMethod
类方法,将实例方法 say666
的实现指向 sayeMethod
方法实现,如下所示
- (void)sayeMethod {
NSLog(@"%s", __func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(say666)) {
NSLog(@"%s 来了", __func__);
IMP imp = class_getMethodImplementation(self, @selector(sayeMethod));
Method method = class_getInstanceMethod(self, @selector(sayeMethod));
const char *type = method_getTypeEncoding(method);
return class_addMethod(self, sel, imp, type);
}
return [super resolveInstanceMethod:sel];
}
重新运行,其打印结果如下
消息转发
包括快速转发和慢速转发
快速转发
当慢速查找,以及动态方法决议均没有找到实现时,会走消息转发,首先是进行快速消息转发,即 forwardingTargetForSelector
- 如果返回消息接收者,在消息接收者中查找
- 如果返回nil,则进入下一步 `慢速转发`
验证
针对前面提到的崩溃,如果动态方法决议也没有找到实现,我们使用另外一中方法解决。我们创建一个 LCPeople
类,声明并实现 say666
实例方法
@interface LCPeople : NSObject
- (void)say666;
@end
@implementation LCPeople
- (void)say666 {
NSLog(@"%s", __func__);
}
@end
在 LCPerson
类中重写 forwardingTargetForSelector
方法,将 LCPerson
的实例方法的消息接收者指定为 LCPeople
的对象,如下:
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
return [LCPeople alloc];
}
再次运行,执行结果如下
慢速转发
如果在上一步(快速转发)中还是没有找到,还有最后的一次挽救机会,即在 LCPerson
中重写 methodSignatureForSelector
,如下所示
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"%s", __func__);
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"%s - %@",__func__,anInvocation);
}
运行代码,执行结果如下
从打印结果发现 forwardInvocation
方法中不对 invocation
进行处理,也不会崩溃报错。
探索
在前面提到重写 resolveInstanceMethod:
方法时,会执行两次,这是为什么呢?首先我们去源码调用的地方,在 resolveInstanceMethod
源码实现中看到触发 resolveInstanceMethod:
方法的代码
我们打个断点,看下调用堆栈,看下到底发生了什么,运行代码,第一次打印 来了
时,bt
打印堆栈信息
执行下一步,知道打印第二次来了,bt
打印堆栈信息
以上我们可以看到,第一次打印时,sel
为 say666
,第二次打印时,可以看到是通过 CoreFoundation
的-[NSObject(NSObject) methodSignatureForSelector:] 方法,然后通过 class_getInstanceMethod
再次进入动态方法决议。
消息转发流程图
总结
结合前面2篇的解析,objc_msgSend
消息发送的流程就分析完成了,总的概括下
快速查找
:在类的缓存
中查找方法的实现慢速查找
:缓存中没有找到方法的实现,则去类的方法列表
中查找,如果没有找到则去继承链的缓存和方法列表
中查找动态方法决议
:如果慢速查找还是没有找到时,尝试一次动态方法决议
,即重写resolveInstanceMethod
/resolveClassMethod
方法消息转发
:如果动态方法决议还是实现,则进行消息转发:快速转发+慢速转发消息转发没有实现,则程序崩溃:
unrecognized selector sent to instance xxx