前面两篇文章我们探索了消息查找的流程,包括快速查找和慢速查找,本文开始探索消息查找未找到时候的处理流程。
unrecognized selector unrecognized selector
是我们开发中很熟悉的一种错误,就是找不到方法的时候报错的提示,比如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // JSPerson.h @interface JSPerson : NSObject - (void)sayNB; @end // JSPerson.m @implementation JSPerson @end // main.m int main(int argc, const char * argv[]) { @autoreleasepool { JSPerson *person = [JSPerson alloc]; [person sayNB]; } return 0; }
我们定义一个JSPerson
类,声明一个sayNB
方法,但是并没有实现,因为oc
是一种动态语言,可以在运行时添加方法实现,所以没有实现编译期并不会报错。在main
函数里实例化一个JSPerson
对象,调用sayNB
方法,这个时候程序就会崩溃,报错:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[JSPerson sayNB]: unrecognized selector sent to instance 0x101844590'
。
这个错误是怎么来的呢,我们从上一节最后方法找不到的地方开始看,当没有找到方法实现时,lookUpImpOrForward
方法返回值是imp = forward_imp;
即_objc_msgForward_impcache
。objc
源码工程里全局搜索这个关键字,在objc-msg-arm64.s
文件的745
行找到实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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] ///返回x17 TailCallFunctionPointer x17 END_ENTRY __objc_msgForward
这段代码比较简单,调用了__objc_forward_handler
方法,继续搜索__objc_forward_handler
方法,并没有找到方法实现,根据前面的经验,去掉_
继续搜索objc_forward_handler
,在objc_runtime.mm
文件中找到了实现:
1 2 3 4 5 6 7 8 9 10 __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;
这段代码就显而易见了,找不到方法的时候会执行unrecognized selector
报错。
动态方法决议 了解了unrecognized selector
后我们回到lookUpImpOrForward
的流程,当递归完查找所有父类流程没有找到方法实现会继续执行方法后面的代码:
1 2 3 4 if (slowpath(behavior & LOOKUP_RESOLVER)) { behavior ^= LOOKUP_RESOLVER; return resolveMethod_locked(inst, sel, cls, behavior); }
behavior
是lookUpImpOrForward
方法的最后一个参数,通过找到方法的调用behavior=LOOKUP_INITIALIZE | LOOKUP_RESOLVER
=3
。LOOKUP_RESOLVER=2
1 2 3 4 5 6 7 enum { LOOKUP_INITIALIZE = 1 , LOOKUP_RESOLVER = 2 , LOOKUP_NIL = 4 , LOOKUP_NOCACHE = 8 , };
这个if
代码块其实是巧妙的使用了单例
思想只会执行一次:
初始behavior & LOOKUP_RESOLVER = 3 & 2
= 2
,条件为true
执行
behavior ^= LOOKUP_RESOLVER = 3 ^ 2 = 1
如果后面再次进行if
判断,behavior & LOOKUP_RESOLVER = 1 & 2 = 0
,条件为false
就不会执行代码块。
前面if
代码块会执行方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 static NEVER_INLINE IMPresolveMethod_locked(id inst, SEL sel, Class cls, int behavior) { runtimeLock.assertLocked(); ASSERT(cls->isRealized()); runtimeLock.unlock(); if (! cls->isMetaClass()) { resolveInstanceMethod(inst, sel, cls); } else { resolveClassMethod(inst, sel, cls); if (!lookUpImpOrNilTryCache(inst, sel, cls)) { resolveInstanceMethod(inst, sel, cls); } } return lookUpImpOrForwardTryCache(inst, sel, cls, behavior); }
实例方法流程 我们先看实例方法,实例方法会调用resolveInstanceMethod
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 static void resolveInstanceMethod (id inst, SEL sel, Class cls) { runtimeLock.assertUnlocked(); ASSERT(cls->isRealized()); SEL resolve_sel = @selector(resolveInstanceMethod:); if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(true ))) { return ; } BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; bool resolved = msg(cls, resolve_sel, sel); 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 { _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)); } } }
可以看到,其实是在类中添加一个resolveInstanceMethod:
进行处理,我们在前面例子的基础上给JSPerson
类添加一个resolveInstanceMethod:
方法处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @implementation JSPerson - (void)saySomethingDefalut{ NSLog(@"%@ - %s",self , __func__); } + (BOOL)resolveInstanceMethod:(SEL)sel{ NSLog(@"resolveInstanceMethod :%@-%@",self,NSStringFromSelector(sel)); if (sel == @selector(sayNB)) { IMP sayDefalutImp = class_getMethodImplementation(self, @selector(saySomethingDefalut)); Method method = class_getInstanceMethod(self, @selector(saySomethingDefalut)); const char *type = method_getTypeEncoding(method); return class_addMethod(self, sel, sayDefalutImp, type); } return [super resolveInstanceMethod:sel]; } @end
重新运行main
方法,发现程序可以正常运行了,控制台打印结果
1 2 3 resolveInstanceMethod :JSPerson-sayNB resolveInstanceMethod :JSPerson-encodeWithOSLogCoder:options:maxLength: <JSPerson: 0x10194fab0 > - -[JSPerson saySomethingDefalut]
通过打印结果我们看到确实进入了resolveInstanceMethod
,而且最终执行了saySomethingDefalut
方法,实例方法的动态方法解析正常流程就是这样了。
还有一个异常情况 就是,如果我们不添加方法处理,只是打印执行NSLog
方法:
1 2 3 4 + (BOOL)resolveInstanceMethod:(SEL)sel{ NSLog(@"resolveInstanceMethod :%@-%@",self,NSStringFromSelector(sel)); return [super resolveInstanceMethod:sel]; }
这个时候程序崩溃是毋庸置疑的,因为这样写相当于没有处理,但是这时候控制台的打印结果是:
1 2 3 resolveInstanceMethod :JSPerson-sayNB resolveInstanceMethod :JSPerson-sayNB -[JSPerson sayNB]: unrecognized selector sent to instance 0x10193a810
resolveInstanceMethod
执行了两次 ,这是为什么呢,我们在resolveInstanceMethod
方法添加断点,使用bt
命令查看调用栈:
类方法流程 类方法的流程就是在resolveClassMethod
实现,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 static void resolveClassMethod (id inst, SEL sel, Class cls) { runtimeLock.assertUnlocked(); ASSERT(cls->isRealized()); ASSERT(cls->isMetaClass()); if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) { return ; } 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); } } BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel); 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 { _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:
的类方法处理,同样前面的例子我们验证一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @interface JSPerson : NSObject + (void)sayHowAreYou; @end @implementation JSPerson + (void)sayAndYou{ NSLog(@"%@ - %s",self , __func__); } + (BOOL)resolveClassMethod:(SEL)sel{ NSLog(@"resolveClassMethod :%@-%@",self,NSStringFromSelector(sel)); if (sel == @selector(sayHowAreYou)) { IMP sayAndYouImp = class_getMethodImplementation(objc_getMetaClass("JSPerson"), @selector(sayAndYou)); Method method = class_getInstanceMethod(objc_getMetaClass("JSPerson"), @selector(sayAndYou)); const char *type = method_getTypeEncoding(method); return class_addMethod(objc_getMetaClass("JSPerson"), sel, sayAndYouImp, type); } return [super resolveClassMethod:sel]; } @end int main(int argc, const char * argv[]) { @autoreleasepool { [JSPerson sayHowAreYou]; } return 0; }
编译运行发现程序正常运行了,打印结果为
1 2 resolveClassMethod :JSPerson-sayHowAreYou JSPerson - +[JSPerson sayAndYou]
1 2 3 4 5 6 7 8 9 else{ // try [nonMetaClass resolveClassMethod:sel] // and [cls resolveInstanceMethod:sel] //类方法 解析 resolveClassMethod(inst, sel, cls); if (!lookUpImpOrNilTryCache(inst, sel, cls)) { resolveInstanceMethod(inst, sel, cls); } }
细心地我们现在会发现在上面这段代码也就是类方法解析这个流程还有一个resolveInstanceMethod(inst, sel, cls);
解析,这个原因是类方法在元类里是以实例方法的形式存在的,所以元类里resolveInstanceMethod
对方法做了处理也会正常运行。
AOP思想处理动态方法决议 通过上面的动态方法解析流程,和前面我们探索的isa
走位图和继承链 ,我们可以使用AOP
的思想,全局处理类的动态方法解析。具体实现就是在NSObject
的分类中添加resolveInstanceMethod:
类方法,处理方法的动态解析,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 @interface NSObject (JS) @end @implementation NSObject (LG) - (void)saySomethingDefalut{ NSLog(@"%@ - %s",self , __func__); } + (BOOL)resolveInstanceMethod:(SEL)sel{ NSLog(@"resolveInstanceMethod :%@-%@",self,NSStringFromSelector(sel)); if (sel == @selector(sayNB)) { IMP sayDefalutImp = class_getMethodImplementation(self, @selector(saySomethingDefalut)); Method method = class_getInstanceMethod(self, @selector(saySomethingDefalut)); const char *type = method_getTypeEncoding(method); return class_addMethod(self, sel, sayDefalutImp, type); } return NO; } @end @interface JSPerson : NSObject - (void)sayNB; @end @implementation JSPerson @end int main(int argc, const char * argv[]) { @autoreleasepool { JSPerson *person = [JSPerson alloc]; [person sayNB]; } return 0; }
运行项目发现正常运行,打印结果为:
1 2 resolveInstanceMethod :JSPerson-sayNB <JSPerson: 0x100593ed0 > - -[NSObject(LG) saySomethingDefalut]
总结 本节我们主要探讨了动态方法解析流程,分实例方法和类方法两种情况
实例方法是在类中实现resolveInstanceMethod:
的类方法,处理方法得动态绑定到异常处理的方法。
类方法分两部分查找
搜索当前类的resolveClassMethod:
方法。
搜索类的元类的resolveInstanceMethod:
方法
我们可以利用AOP思想在根类的分类
中实现resolveInstanceMethod:
从而完成全局方法的动态解析。
如果没有进行动态方法解析,接下来就会进入消息转发的流程,我们下一节探索。