上篇文章 objc_msgSend 我们分析了底层从 cache 或 methodList 中获取方法的过程,我们知道了在 lookUpImpOrForward 方法中没找到方法后imp指针会被置为 forward_imp,这篇我们沿着这个思路查找 forward_imp 的内容,分析方法最后都没有被找到方法后的消息转发流程
NEVER_INLINE
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
Class curClass;
runtimeLock.assertUnlocked();
......
}
1、报错转发
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
// No implementation found, and method resolver didn't help.
// 没有找到实现,方法解析器没有帮助
// Use forwarding.
imp = forward_imp;
break;
}
1.1、_objc_msgForward_impcache
- forward_imp 是通过 _objc_msgForward_impcache 得来,直接跳转的内容不是具体实现,所以我们全局搜索并找到arm64架构下的实现,又进入到了汇编代码
- 其中实现的是 __objc_msgForward 方法,内容在紧邻的下边,比较重要的方法是 __objc_forward_handler
1.2、_objc_forward_handler
- 全局搜索 _objc_forward_handler 的实现,可以看到熟悉的
方法未找到的报错就是在这完成的,而且只是 以是否为元类来判断是类方法还是实例方法// 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); }
2、方法解析器
- 在进行报错转发前,还有两点我们不能忘记
- 1.1 的图示英文注释写了:"没有找到实现,方法解析器没有帮助",从中我们可以得知,在进行报错转发前,系统尝试了对一个叫做
方法解析器的东西进行求助,我们要分析一下其中进行了什么操作 - 虽然将imp指针置为了 forward_imp指针,但也仅仅是重置指针,实际还是需要调用的,在何处进行的调用也是需要分析一下的
- 1.1 的图示英文注释写了:"没有找到实现,方法解析器没有帮助",从中我们可以得知,在进行报错转发前,系统尝试了对一个叫做
2.1、执行一次方法解析器
-
在 lookUpImpOrForward 方法的for循环找方法、imp指针置为forward_imp结束后,还要执行下边的代码
// No implementation found. Try method resolver once. // 没有实现.尝试一次方法解析器 if (slowpath(behavior & LOOKUP_RESOLVER)) { behavior ^= LOOKUP_RESOLVER; return resolveMethod_locked(inst, sel, cls, behavior); }那么问题来了,既然是在for循环后才执行的,为什么在for循环中就提到了 "方法解析器没有帮助" 呢?往下分析就知道了
-
方法解析器中首先对
behavior和LOOKUP_RESOLVER取与操作的结果进行了判断 -
进入判断后对
behavior和LOOKUP_RESOLVER取异或操作,如果后续未对未实现方法添加imp,内部方法中还会执行 lookUpImpOrForward,这次传入的 behavior 一定不会通过与操作的判断,保证了只执行一次方法解析器,然后就可以执行 forward_imp 了
2.2、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);
//如果缓存中没有找到,那么最终还是走resolveInstanceMethod
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);
}
- 这里判断一下元类,是元类则调用
resolveClassMethod,如果是类则调用resolveInstanceMethod - 如果缓存中没有找到,那么最终还是走 resolveInstanceMethod,
底层中不分类方法和实例方法,最终归宿都是实例方法,resolveClassMethod 只是用来缓存方法,用来简化查找流程;而且按照正常思路,实例方法动态添加写到类中,那么类方法动态添加就应该写到元类中,但是元类是一个我们看不见摸不着的东西,如果不使用 resolveClassMethod 的话,查找动态添加的imp方法就需要往上一直找到NSObject才行,那样会大幅降低查找效率(resolveInstanceMethod 是真爱,resolveClassMethod 只是意外~)
2.2.1、resolveInstanceMethod
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
//objc_msgSend 执行 resolveInstanceMethod:
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);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
//缓存动态添加imp结果
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));
}
}
}
- 提供了第一次补救机会,如果在类中手动实现
+ (BOOL)resolveInstanceMethod:(SEL)sel方法,为没有实现的方法动态添加imp指针,则可避免报错转发
2.2.2、resolveClassMethod
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)resolveClassMethod:(SEL)sel为方法动态添加 imp指针,添加的方法可为实例方法,也可为类方法,但是要注意接收的对象是 类 还是 元类
2.3、lookUpImpOrForwardTryCache --> _lookUpImpTryCache
IMP lookUpImpOrForwardTryCache(id inst, SEL sel, Class cls, int behavior)
{
return _lookUpImpTryCache(inst, sel, cls, behavior);
}
因为前边提供了动态添加imp的补救机会,所以再在缓存里尝试找imp,如果没把握住机会,则会再走一遍 lookUpImpOrForward,这次就不会再触发方法解析器了
ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertUnlocked();
if (slowpath(!cls->isInitialized())) {
// see comment in lookUpImpOrForward
return lookUpImpOrForward(inst, sel, cls, behavior);
}
IMP imp = cache_getImp(cls, sel);
if (imp != NULL) goto done;
#if CONFIG_USE_PREOPT_CACHES
if (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) {
imp = cache_getImp(cls->cache.preoptFallbackClass(), sel);
}
#endif
if (slowpath(imp == NULL)) {
//这就是前边提到的还会走一遍 lookUpImpOrForward 的地方,因此进行与运算确保方法解析器只执行一次
return lookUpImpOrForward(inst, sel, cls, behavior);
}
done:
if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
return nil;
}
//用户进行了补救操作,从缓存中拿到了imp指针
return imp;
}
3、消息的快速转发
-
如果未对方法动态添加imp指针,那么就会进入消息的快速转发流程,提供了第二次补救机会
-
-(id)forwardingTargetForSelector:(SEL)aSelector -
可以专门写一个处理其他类未实现方法的类,对这些内容进行收集上报,帮助改善代码
4、消息的慢速转发
- 快速转发未实现则进入慢速转发流程,提供了第三次补救机会
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector- (void)forwardInvocation:(NSInvocation *)anInvocation
总结
- lookUpImpOrForward 方法中未找到方法imp指针 --> 方法解析器
- resolveMethod_locked -->
resolveInstanceMethod/resolveClassMethod(resolveInstanceMethod 的近路) --> cache_getImp(再找一遍cache,如果动态添加了imp就能在cache中找到) - 缓存方法(动态添加了imp)/ 再执行 lookUpImpOrForward(未动态添加imp、
方法解析器的behavior值必为0) - 消息的快速转发:
forwardingTargetForSelector,本类实现不了,找其他有同名方法的类去实现 - 消息的慢速转发:
methodSignatureForSelector、forwardInvocation,更灵活的防止崩溃 - 报错转发:完蛋了
注: 可以在这些方法中交换imp指针,实现hook方法与埋点