通过这篇文章可以获得什么:
- 探索消息转发的起因
- 控制台打印为什么会提示unrecognized selector sent to instance?
- 控制台打印查找流程图
- 动态方法决议
- 位运算实现单例解读
- resolveMethod_locked相关源码解读
- 实例方法的动态决议
- 类方法的动态决议
- 整合实例对象/类对象的动态方法决议
- oop与aop简单说明
探索消息转发的起因
- 我在
FFPerson类里面创建了一个实例方法likeGirls,但是并未实现 - 然后我在
main.m文件中初始化FFPerson类,并调用likeGirls方法 - 出现经典错误:
'-[FFPerson likeGirls]: unrecognized selector sent to instance 0x1006106e0',无法找到该方法的实例,即未实现。
案例代码
@interface FFPerson : NSObject
//声明了一个实例方法
- (void)likeGirls;
@end
@implementation FFPerson
//该实例方法并没有实现
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
//初始化FFPerson的实例对象
FFPerson *person = [FFPerson alloc];
//调用likeGirls实例方法
[person likeGirls];
}
return 0;
}
控制台经典报错:
2021-07-04 09:58:10.002598+0800 001-探索消息转发的起因[12184:1009168] -[FFPerson likeGirls]: unrecognized selector sent to instance 0x1006106e0
2021-07-04 09:58:10.017465+0800 001-探索消息转发的起因[12184:1009168] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[FFPerson likeGirls]: unrecognized selector sent to instance 0x1006106e0'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff205a56af __exceptionPreprocess + 242
1 libobjc.A.dylib 0x00007fff202dd3c9 objc_exception_throw + 48
2 CoreFoundation 0x00007fff20627c85 -[NSObject(NSObject) __retain_OA] + 0
3 CoreFoundation 0x00007fff2050d07d ___forwarding___ + 1467
4 CoreFoundation 0x00007fff2050ca38 _CF_forwarding_prep_0 + 120
6 libdyld.dylib 0x00007fff2044e621 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[FFPerson likeGirls]: unrecognized selector sent to instance 0x1006106e0'
terminating with uncaught exception of type NSException
(lldb)
截图补充:
疑问点一:当方法未实现的时候为什么会报
unrecognized selector sent to instance这个错误呢?
通过前几次文章的分享,知道了一个消息的查找流程:
快速查找流程(已完成探索)->慢速查找流程(已完成探索)->动态方法转发流程(本篇探索内容)
当消息进入到慢速查找流程的时候最终会停留在LookUpImpOrForward这个核心方法上,那么我此篇内容依然接续着慢速查找流程无法匹配的情况看LookUpImpOrForword的后续操作,所以入手点就在这个方法,全局搜索:
objc4源码解答(截取关键源码):
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
for (unsigned attempts = unreasonableClassCount();;) {
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
//当消息的快速查找、慢速查找流程都走完还找不到的情况下
//imp会给一个默认值forward_imp,进入消息转发流程
imp = forward_imp;
break;
}
}
return imp;
}
通过LookUpImpOrForward这个方法可以得知,imp在未找到的时候会被默认赋值一个forward_imp,forward_imp只是一个符号,它代表的是(IMP)_objc_msgForward_impcache,接下来去全局搜索(IMP)_objc_msgForward_impcache:
继续全局查找__objc_msgForward,我这里还是只关心arm64的架构:
全局搜索__objc_forward_handler,发现没有我想要的结果,没有找到具体的实现地方
将__objc_forward_handler的下划线“_”删除一个,再次搜索_objc_forward_handler
到这里,守得云开见月明,看到了控制台打印为什么会提示unrecognized selector sent to instance?
探索流程图:
总结:
由控制台打印信息
unrecognized selector sent to instance为出发点,据悉向下深挖方法的响应流程通过对
LookUpImpOrForward的分析得出在慢速查找未找到的时候,给imp默认赋值forward_imp,即objc_msgForward_impcache通过对
objc_msgForward_impcache的查找,发现在汇编中它只是一个中间函数,真正指向的是objc_msgForward对
objc_msgForward的分析得出,方法内调用了objc_forward_handle获取返回值存到x17寄存器,再由TailCallFunctionPointer方法跳转到x17寄存器内真正指向的imp地址
_objc_forward_handler会给定默认的实现objc_defaultForwardHandle,当imp最终的查找流程全部走完的时候,还未找到imp,那么此时就会进入objc_defaultForwardHandle函数,将错误信息打印出来。
动态方法决议
当上述一系列的方法都走完之后,无法找到sel对应的imp,这个是后循环查找结束了,接下来进入动态方法决议
objc4源码解答(截取关键源码):
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
for (unsigned attempts = unreasonableClassCount();;) {
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
//当消息的快速查找、慢速查找流程都走完还找不到的情况下
//imp会给一个默认值forward_imp,进入消息转发流程
imp = forward_imp;
break;
}
//找到了sel对应的imp
if (fastpath(imp)) {
goto done;
}
}
/**
* 如果遍历查找的过程找到了,会跳过此步骤,取到done分支,进行后续操作
* 如果找不到,会进行下面这个算法,最终进入resolveMethod_locked函数
* 此算法真正达到的目的为单例,保证一个lookUpImpOrForward
* 只执行一次resolveMethod_locked
*/
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
return imp;
}
位运算实现单例解读
操作符号解读:
-
&:按位与,是双目运算符。其功能是参与运算的两数各对应的二进位相与。只有对应的两个二进位都为1时,结果位才为1。 -
^:按位异或,比较的是二进制位,相同位置数字不同为1,相同为0 -
^=:按位异或,比较的是二进制位,相同位置数字不同为1,相同为0,将结果赋值为运算符左边。
前提条件(初始值)
behavior = 3
LOOKUP_RESOLVER = 2
初次判断操作
操作一:behavior & LOOKUP_RESOLVER = 3 & 2 = 0x11 & 0x10 = 0x10 = 2
重置behaivor
操作二:behavior = behavior ^ LOOKUP_RESOLVER = 3 ^ 2 = 0x11 ^ 0x10 = 0x01 = 1
再次进入判断操作(第二次至无限次)
操作三:behavior & LOOKUP_RESOLVER = 1 & 2 = 0x01 & 0x10 = 0x00 = 0
结论
保证了每一个lookUpImpOrForward函数最多只能执行一次resolveMethod_locked(动态方法决议),直到behavior被重新赋值
resolveMethod_locked
此方法存在的意义:当你调用了一个方法的时候,第一进入消息的快速查询流程 -> 然后进入消息的慢速查找流程,当底层源码已经给你方法查找了2遍之后依然找不到你实现的地方,此时imp=nil,理论上来讲程序应该崩溃,但是在开发者的角度上来讲,此做法会令这个框架不稳定,或者说这个系统很不友善。所以此框架决定再给你一次机会,让你重新拯救地球,为你提供了一个自定义的imp返回的机会,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()) {
//imp为实例方法
resolveInstanceMethod(inst, sel, cls);
}
else {
//imp为类方法
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// 经过resolveInstanceMethod函数很有可能已经对sel对应imp完成了动态添加
// 所以再一次尝试查找
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
此函数里面有三个关键的函数:
-
resolveInstanceMethod:实例方法动态添加imp -
resolveClassMethod:类方法动态添加imp -
lookUpImpOrForwardTryCache,当完成添加之后,回到之前的慢速查找流程再来一遍
resolveInstanceMethod
/*********************************************************
* 解析实例方法
* 调用+resolveInstanceMethod,寻找要添加到类cls的方法。
* cls 可能是元类或非元类。
* 不检查该方法是否已经存在。
*********************************************************/
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
//当你为实现resolveInstanceMethod的时候,此处也不会进入return
//因为系统给resolveInstanceMethod函数默认返回NO,即默认实现了
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
return;
}
//系统会在此处为你发送一个消息resolve_sel
//当你的这个类检测了这个消息,并且做了处理
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, sel);
//那么此时系统会重新查找,此函数最终会触发LookUpImpOrForward
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));
}
}
}
resolveClassMethod
/*********************************************************
* 解析类方法
* 调用+resolveClass 方法,寻找要添加到类cls 的方法。
* cls 应该是一个元类。
* 不检查该方法是否已经存在。
*********************************************************/
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
//当你为实现resolveClassMethod的时候,此处也不会进入return
//因为系统给resolveClassMethod函数默认返回NO,即默认实现了
if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
// Resolver not implemented.
return;
}
//nonmeta容错处理
Class nonmeta;
{
mutex_locker_t lock(runtimeLock);
nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
if (!nonmeta->isRealized()) {
_objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
nonmeta->nameForLogging(), nonmeta);
}
}
//系统会在此处为你发送一个消息resolveClassMethod
//当你的这个类检测了这个消息,并且做了处理
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);
//那么此时系统会重新查找,此函数最终会触发LookUpImpOrForward
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));
}
}
}
resolveClassMethod & resolveInstanceMethod
//默认返回NO,当用户不实现这个方法的时候,程序也不会return
+ (BOOL)resolveClassMethod:(SEL)sel {
return NO;
}
//默认返回NO,当用户不实现这个方法的时候,程序也不会return
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return NO;
}
lookUpImpOrForwardTryCache
过度函数
IMP lookUpImpOrForwardTryCache(id inst, SEL sel, Class cls, int behavior)
{
return _lookUpImpTryCache(inst, sel, cls, behavior);
}
_lookUpImpTryCache
通过动态添加方法之后,再次尝试查找sel对应的最新添加的imp
ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertUnlocked();
//当类未初始化的时候,进入lookUpImpOrForward
//在里面处理不缓存任何方法
if (slowpath(!cls->isInitialized())) {
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)) {
return lookUpImpOrForward(inst, sel, cls, behavior);
}
done:
//此判断是当前imp已经存在了,并且这个imp是默认赋值的forward_imp
//此时返回imp为nil;
if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
return nil;
}
return imp;
}
疑问点二:resolveMethod_locked与resolveInstanceMethod函数都会执行lookUpImpOrNilTryCache,为什么要执行2遍呢?
实例方法的动态决议
阶段一:按照源码还原
通过源码已得知会给开发者一次弥补的机会,通过resolveInstanceMethod这个方法,这里在程序内模拟一下这个方法;
@implementation FFPerson
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"手动介入resolveInstanceMethod :%@-%@",self,NSStringFromSelector(sel));
return [super resolveInstanceMethod:sel];
}
@end
控制台打印结果:
021-07-04 16:29:18.779380+0800 001-探索消息转发的起因[14290:1164992] 手动介入resolveInstanceMethod :FFPerson-likeGirls
2021-07-04 16:29:18.779920+0800 001-探索消息转发的起因[14290:1164992] 手动介入resolveInstanceMethod :FFPerson-likeGirls
2021-07-04 16:29:18.780020+0800 001-探索消息转发的起因[14290:1164992] -[FFPerson likeGirls]: unrecognized selector sent to instance 0x100606150
2021-07-04 16:29:18.780574+0800 001-探索消息转发的起因[14290:1164992] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[FFPerson likeGirls]: unrecognized selector sent to instance 0x100606150'
依然报错是在意料之中的事情,因为此时方法还并未实现,但是在崩溃之前打印了我手动介入的信息,也就是说,在崩溃之前有补救的办法。
阶段二:实现\动态添加IMP
案例代码
@implementation FFPerson
//创建一个IMP
- (void)prettyGirls {
NSLog(@"%s",__func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(likeGirls)) {
//当sel==likeGirls,动态给这个sel指定一个新的imp(prettyGirls)
IMP prettyGirlsImp = class_getMethodImplementation(self, @selector(prettyGirls));
Method method = class_getInstanceMethod(self, @selector(prettyGirls));
const char * type = method_getTypeEncoding(method);
return class_addMethod(self, sel, prettyGirlsImp, type);
}
NSLog(@"手动介入resolveInstanceMethod :%@-%@",self,NSStringFromSelector(sel));
return [super resolveInstanceMethod:sel];
}
@end
控制台打印结果:
2021-07-04 16:46:17.907703+0800 001-探索消息转发的起因[14403:1174269] -[FFPerson prettyGirls]
类方法的动态决议
案例代码
@interface FFPerson : NSObject
//声明了一个实例方法
+ (void)enjoyLife;
@end
@implementation FFPerson
//该类方法并没有实现
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
//初始化FFPerson的实例对象
FFPerson *person = [FFPerson alloc];
//调用未实现的类方法
[FFPerson enjoyLife];
}
return 0;
}
控制台经典报错
2021-07-04 17:15:18.903974+0800 001-探索消息转发的起因[14552:1188609] +[FFPerson enjoyLife]: unrecognized selector sent to class 0x100008188
2021-07-04 17:15:18.904697+0800 001-探索消息转发的起因[14552:1188609] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[FFPerson enjoyLife]: unrecognized selector sent to class 0x100008188'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff205a56af __exceptionPreprocess + 242
1 libobjc.A.dylib 0x00007fff202dd3c9 objc_exception_throw + 48
2 CoreFoundation 0x00007fff20627bdd __CFExceptionProem + 0
3 CoreFoundation 0x00007fff2050d07d ___forwarding___ + 1467
4 CoreFoundation 0x00007fff2050ca38 _CF_forwarding_prep_0 + 120
6 libdyld.dylib 0x00007fff2044e621 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[FFPerson enjoyLife]: unrecognized selector sent to class 0x100008188'
terminating with uncaught exception of type NSException
(lldb)
阶段一:按照源码还原
通过源码已得知会给开发者一次弥补的机会,通过resolveInstanceMethod这个方法,这里在程序内模拟一下这个方法;
@implementation FFPerson
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"手动介入resolveInstanceMethod :%@-%@",self,NSStringFromSelector(sel));
return [super resolveInstanceMethod:sel];
}
@end
控制台打印
2021-07-04 17:19:48.778680+0800 001-探索消息转发的起因[14583:1191738] 手动介入resolveInstanceMethod :FFPerson-_dynamicContextEvaluation:patternString:
2021-07-04 17:19:48.791081+0800 001-探索消息转发的起因[14583:1191738] 手动介入resolveInstanceMethod :FFPerson-enjoyLife
2021-07-04 17:19:48.791255+0800 001-探索消息转发的起因[14583:1191738] +[FFPerson enjoyLife]: unrecognized selector sent to class 0x1000081a8
依然报错是在意料之中的事情,因为此时方法还并未实现,但是在崩溃之前打印了我手动介入的信息,也就是说,在崩溃之前有补救的办法。
阶段二:实现\动态添加IMP
案例代码
@implementation FFPerson
//为元类对象创建创建一个IMP
+ (void)enjoyTime {
NSLog(@"%@ - %s",self,__func__);
}
+ (BOOL)resolveClassMethod:(SEL)sel {
if (sel == @selector(enjoyLife)) {
IMP enjoyTimeImp = class_getMethodImplementation(objc_getMetaClass("FFPerson"), @selector(enjoyTime));
Method method = class_getInstanceMethod(objc_getMetaClass("FFPerson"), @selector(enjoyTime));
const char * type = method_getTypeEncoding(method);
return class_addMethod(objc_getMetaClass("FFPerson"), sel, enjoyTimeImp, type);
}
NSLog(@"手动介入resolveInstanceMethod :%@-%@",self,NSStringFromSelector(sel));
return [super resolveClassMethod:sel];
}
控制台打印结果:
2021-07-04 17:40:30.032916+0800 001-探索消息转发的起因[14750:1203053] FFPerson - +[FFPerson enjoyTime]
结论:
通过手动添加
resolveInstanceMethod/resolveClassMethod,先打印验证在崩溃前有没有开发者的操作空间通过判断当前查找的
sel动态添加一个新的imp经过论证,此方法是
可以的。此时sel(likeGirls/enjoyLife)对应实现的imp不再是likeGirls/enjoyLife,而是变成了我动态指定的prettyGirls/enjoyTime
疑问点三:类动态方法决议会进入resolveClassMethod,然后根据判断有可能会再次进入resolveInstanceMethod,为什么?
整合实例对象/类对象的动态方法决议
回到现实,日常开发中不可能每个类中都实现一份resolveInstanceMethod/resolveClassMethod,这是比较真实的场景,那么这里通过创建NSObject分类的方式将此实现都放在分类中:
NSObject+FF
@implementation NSObject (FF)
//为实例对象创建一个IMP
- (void)prettyGirls {
NSLog(@"%s",__func__);
}
//为元类对象创建创建一个IMP
+ (void)enjoyTime {
NSLog(@"%@ - %s",self,__func__);
}
#pragma clang diagnostic push
// 让编译器忽略错误
#pragma clang diagnostic ignored "-Wundeclared-selector"
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"手动介入resolveInstanceMethod :%@-%@",self,NSStringFromSelector(sel));
if (sel == @selector(likeGirls)) {
IMP prettyGirlsImp = class_getMethodImplementation(self, @selector(prettyGirls));
Method method = class_getInstanceMethod(self, @selector(prettyGirls));
const char * type = method_getTypeEncoding(method);
return class_addMethod(self, sel, prettyGirlsImp, type);
} else if (sel == @selector(enjoyLife)) {
IMP enjoyTimeImp = class_getMethodImplementation(objc_getMetaClass("FFPerson"), @selector(enjoyTime));
Method method = class_getInstanceMethod(objc_getMetaClass("FFPerson"), @selector(enjoyTime));
const char * type = method_getTypeEncoding(method);
return class_addMethod(objc_getMetaClass("FFPerson"), sel, enjoyTimeImp, type);
}
return NO;
}
#pragma clang diagnostic pop
@end
main.m
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
FFPerson *person = [FFPerson alloc];
//调用未实现的实例方法
[person likeGirls];
//调用未实现的类方法
[FFPerson enjoyLife];
}
return 0;
}
打印结果(部分关键打印)
2021-07-04 18:06:55.491847+0800 001-探索消息转发的起因[14926:1216030] 手动介入resolveInstanceMethod :FFPerson-likeGirls
2021-07-04 18:06:55.491952+0800 001-探索消息转发的起因[14926:1216030] -[NSObject(FF) prettyGirls]
2021-07-04 18:06:55.492192+0800 001-探索消息转发的起因[14926:1216030] 手动介入resolveInstanceMethod :FFPerson-enjoyLife
2021-07-04 18:06:55.502342+0800 001-探索消息转发的起因[14926:1216030] FFPerson - +[NSObject(FF) enjoyTime]
疑问点四:动态方法决议的作用是什么?Apple为什么这么去设计?
oop与aop
-
oop:面向
对象编程,什么人做什么什么事情,分工非常明确。- 好处:
耦合度很低 - 痛点:有很多
冗余代码,常规解决办法是提取,那么会有一个公共的类,所有人对公共的类进行集成,那么所有人对公共类进行强依赖,也就代表着出现了强耦合
- 好处:
-
aop:面向
切面编程,是oop的延伸- 切点:要切入的
方法和切入的类,比如上述的例子中的enjoyLife和FFPerson - 优点:
对业务无侵入,通过动态方式将某些方法进行注入 - 缺点:做了一些判断,执行了很多
无关代码,包括系统方法,造成性能消耗。会打断apple的方法动态转发流程。
- 切点:要切入的
解答在源码探索过程中提出的疑问点:
当方法未实现的时候为什么会报unrecognized selector sent to instance这个错误呢?
通过上述的论证可以知道当方法未实现的时候,进入方法慢速查找之后,依然找不到,会给方法一个默认值_objc_msgForward_impcache,此默认值通过一系列的查找,最终会找到objc_defaultForwardHandle函数,此函数里面标注了明确的错误信息。
resolveMethod_locked与resolveInstanceMethod函数都会执行lookUpImpOrNilTryCache,为什么要执行2遍呢?
通过调试发现了原因是多执行了respondsToSelector等方法,具体原因未探索清楚,后续补充
类动态方法决议会进入resolveClassMethod,然后根据判断有可能会再次进入resolveInstanceMethod,为什么?
正常的类对象动态方法决议会进入resolveClassMethod,这点是毋庸置疑的,但是类方法查找过程是在元类中查找,那么通过isa的指向图中可以得知,类方法的查找过程也是有继承关系的,会一直向上找,找到superMetaClass,找到rootMetaClass,最终找到NSObject,到这一层,所有的方法对于NSObject来讲都是实例方法所以会调用resolveInstanceMethod。
动态方法决议的作用是什么?Apple为什么这么去设计?
首先这是苹果给开发者的最后一次机会,让我们自行处理一下你找不到的方法。将resolveInstanceMethod方法写在NSNbject的分类中,意味着全局所有方法找不到,我们都可以监听的到。创建一个NSObject分类“项目名_模块_事物”,比如,FF_Home_didClickDetail,此didClickDetail事件未实现,那么第一时间向后台报告,哪个项目,哪个模块,哪个时间导致了crash,让你极致的拯救你的KPI,第二件事就是防止carsh,当时间未实现的时候,跳转到Home。
感悟:
通过此过程的探索,想要成为一个高逼格的开发人员,一定要给别人更多的容错处理。