前言
在之前的的objc_msgSend()
探索中,当调用一个方法后,首先会进入快速查找来匹配sel
对应的imp
;当没有找到时,会进入慢速查找,开始匹配;当依然无法匹配时,苹果给出了一次转发的机会(动态方法决议):
// No implementation found. Try method resolver once.
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
从注释:// No implementation found. Try method resolver once.
也是一目了然。
这篇文章,我们就一起探索下resolveMethod_locked()
的内部实现。
开始
直接看下resolveMethod_locked()
的源码:
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);
}
首先判断当前的cls
是否为元类
,当不是元类
则调用resolveInstanceMethod()
,否则调用resolveClassMethod()
。接下来分别看下两个方法的具体实现:
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
/***********************************************************************
* 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());
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));
}
}
}
该方法一开始的注释写的就很清楚:
- Call +resolveInstanceMethod, looking for a method to be added to class cls.
意味着,resolveInstanceMethod
执行会响应+resolveInstanceMethod
,我们可以通过在类中实现该方法,来接收响应,并做处理。同时我们可以看到发起响应的关键代码:
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);
即通过objc_msgSend
向cls
发送了一个resolveInstanceMethod
消息。
static void resolveClassMethod(id inst, SEL sel, Class cls)
//在cls中添加并实现
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
//自定义动态处理
return [super resolveInstanceMethod:sel];
}
再来看下 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 (!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
中添加并实现该方法来接收响应,关键的触发响应代码,同样是通过objc_msgSend
发送消息,跟resolveInstanceMethod的逻辑如出一辙:
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);
回到我们的类:
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
//添加自定义处理
return [super resolveInstanceMethod:sel];
}
+ (BOOL)resolveClassMethod:(SEL)sel
{
//添加自定义处理
return [super resolveClassMethod:sel];
}
消息转发
当方法决议后依然没有匹配到imp
时,会触发消息转发机制,由libObjC
转向CoreFoundation
处理并触发转发回调,这个可以在Crash时的堆栈信息中看到:
关于
CoreFoundation
的源码这里就不展开验证了(需要借助一些反编译工具查看一些伪代码)。
此时会触发以下几个熟悉的回调函数:
//快速转发流程
- (id)forwardingTargetForSelector:(SEL)aSelector
{
return nil;
}
//慢速转发流程
//1.方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
//2.转发处理
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
// [anInvocation invoke];
}
//当没有实现任何处理时
- (void)doesNotRecognizeSelector:(SEL)aSelector
{
}
在日常的开发中,我们可以在适当的位置,做响应的容错处理代码。
消息转发简单流程图
这里解释下
resolveClassMethod
->resolveInstanceMethod
:
当调用类方法的时候,
Objc
会去该类
的元类
中去查找对应的IMP
,没有找到会去他的父元类
中查找,最终会查询到根元类
,根元类
依然没有找到,会继续去根元类的父类
,即根类(NSObject)
中进行查找,而类中只有实例方法,此时objc内部做了一些处理,当在根元类
无法找到IMP
时,直接响应父类(NSObject类)
的resolveInstanceMethod
方法,可以在如下的源码中体现:
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);
}
总结
至此,我们能看到,在我们程序将要发生崩溃时,我们有两次避免崩溃的机会,即动态方法决议和消息转发处理,前者是在libObjC
下触发,或者在CoreFoundation
下触发。在实际开发中我们应该尽量避免在方法决议层做逻辑处理,而是在更底层的消息转发层做相应的容错处理。