iOS底层探索:动态方法决议与消息转发
注:本文旨在记录笔者的学习过程,仅代表笔者个人的理解,如果有表述不准确的地方,欢迎各位指正!因为涉及到的概念来源自网络,所以如有侵权,也望告知!
前言
本文主要是探索iOS底层方法调用过程中的动态方法决议与消息转发过程。
正文
一、回顾
在文章——iOS底层探索:objc_msgSend慢速查找中,我们讲解到:当方法调用过程中经过快速查询、慢速查询步骤后并未找到相应方法的实现,此时在程序最终抛出报错日志前,会走到动态方法决议的流程中(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);
}
复制代码
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));
}
}
}
复制代码
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));
}
}
}
复制代码
对源码进行分析,我们可以了解到:
a.当方法调用过程中,如果未实现对应方法,动态方法决议的步骤可以对类进行方法的添加,避免报错,相当于给了一次容错的机会。
b.调用实例方法时,如果未实现对应方法,则会进行动态方法决议,调用类方法resolveInstanceMethod。
c.调用类方法时,如果未实现对应方法,则会进行动态方法决议,调用类方法resolveClassMethod
,同时还会调用根类(NSObject)的类方法resolveInstanceMethod
调试验证:
image三、消息转发
从上述源码分析中,我们知道了动态方法决议,但是实际调试过程中我们查看堆栈发现,在动态方法决议之后到最终的系统报错环节中间,CoreFoundation本身也做了一定的操作。
image那么接下来我们就探索一下CoreFoundation,看看这中间到底做了什么?找到CoreFoundation所在位置。
image image通过工具进行反编译查看:
查找__forwarding_prep_0___
,
接着查看_forwarding会发现如下代码:
image image到这里我们就会发现,其实在CoreFoundation内部,对方法调用过程中未找到对应方法的场景也给了开发者两次容错处理的机会:快速消息转发forwardingTargetForSelector
、慢速消息转发(methodSignatureForSelector
、forwardStackInvocation
)。
消息转发的作用:
a.forwardingTargetForSelector
步骤可以通过指定消息转发的对象,快速重定位到消息的处理者,让制定的转发对象进行处理,避免报错。
b.methodSignatureForSelector
、forwardStackInvocation
步骤是将消息封装成NSInvocation格式的漂流瓶,等待有可以处理该消息的对象去处理,同样也可以避免报错。
作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:761407670,不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!