补充
慢速查找流程的realizeAndInitializeIfNeeded_locked方法会主动调用class的initialize方法,此方法在+load、initalize、C++构造方法时自动调用,其流程如下realizeAndInitializeIfNeeded_locked -> realizeAndInitializeIfNeeded_locked -> initializeAndLeaveLocked -> initializeAndMaybeRelock -> initializeNonMetaClass -> callInitialize
void callInitialize(Class cls){((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));asm("");}
动态方法决议
方法找不到的底层原理
开发过程中,经常会遇到经典的unrecongnize selector,其底层是如何实现的?
在lookUpImpOrForward中,初始值是_objc_msgForward_impcache
NEVER_INLINEIMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior){const IMP forward_imp = (IMP)_objc_msgForward_impcache;
点击查看,跳入汇编代码,objc_msgForward_impcache -> objc_msgForward -> __objc_forward_handler
STATIC_ENTRY __objc_msgForward_impcache// No stret specialization.b __objc_msgForwardEND_ENTRY __objc_msgForward_impcacheENTRY __objc_msgForwardadrp x17, __objc_forward_handler@PAGEldr p17, [x17, __objc_forward_handler@PAGEOFF]TailCallFunctionPointer x17END_ENTRY __objc_msgForward
__objc_forward_handler又跳转C++代码,在objc_defaultForwardHandler底层打印实现了unrecongnice selector函数
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;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);}
对象方法动态决议
在lookUpImpOrForward函数中,break跳出for循环后,执行以下单例方法behavior = 3,LOOKUP_RESOLVER = 2, 3 & 2 = 2, 3 ^ 2 = 0, 0 赋值给behavior,保证只走一次
if (slowpath(behavior & LOOKUP_RESOLVER)) { //behavior ^= LOOKUP_RESOLVER;return resolveMethod_locked(inst, sel, cls, behavior);}
resolveMethod_locked中会根据判断会调用resolveInstanceMethod或者resolveClassMethod,resolveClassMethod中多了lookUpImpOrNilTryCache判断,目前是查找当前类的对象是否显示的imp
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior) {if (cls是元类) {resolveClassMethod();if (!lookUpImpOrNilTryCache(inst, sel, cls)) {resolveInstanceMethod(inst, sel, cls);}} else {resolveInstanceMethod(inst, sel, cls);}}
resolveInstanceMethod 会调用msg(cls, resolve_sel, sel),cls是类,所以resolveInstanceMethod是+方法
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(/*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. clsIMP 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));}}}
举例
定义一个teacher类,继承于person类,person类声明了say666方法,teacher类和person类都未实现该方法,调用[[teacher alloc] say666]运行程序是会崩溃,unrecongnice selector
-[LGTeacher say666]: unrecognized selector sent to instance 0x108d0bdb0
在LGTeacher类中,重写resolveInstanceMethod后,添加断点调试,可知在缓存查找失败后,对象方法调用以下函数+(BOOL)resolveInstanceMethod:(SEL)sel,重写resolveInstanceMethod,动态添加新sel sayNB后,可以防止程序崩溃
+ (BOOL)resolveInstanceMethod:(SEL)sel {if (sel == @selector(say666)) {IMP sayNBImp = class_getMethodImplementation(self, @selector(sayNB));Method method = class_getInstanceMethod(self, @selector(sayNB));const char *type = method_getTypeEncoding(method);return class_addMethod(self, sel, sayNBImp, type);}NSLog(@"resolveInstanceMethod :%@-%@",self,NSStringFromSelector(sel));return [super resolveInstanceMethod:sel];}<LGTeacher: 0x108b503f0> - -[LGTeacher sayNB]
类方法也可以做类似操作,在类中类的方式相当于元类中的对象方式
//// 元类的以对象方法的方法+ (BOOL)resolveClassMethod:(SEL)sel{NSLog(@"resolveClassMethod :%@-%@",self,NSStringFromSelector(sel));if (sel == @selector(sayHappy)) {IMP sayNBImp = class_getMethodImplementation(objc_getMetaClass("LGTeacher"), @selector(sayKC));Method method = class_getInstanceMethod(objc_getMetaClass("LGTeacher"), @selector(sayKC));const char *type = method_getTypeEncoding(method);return class_addMethod(objc_getMetaClass("LGTeacher"), sel, sayNBImp, type);}return [super resolveClassMethod:sel];}
难点
在打印过程中,我们发现无论是resolveClassMethod还是resolveInstanceMethod都打印了两次,这是怎么回事呢?
在resolveInstanceMethod我们加入断点,po当前sel得到say666
bool resolved = msg(cls, resolve_sel, sel);
过掉断点,在第二次断点时,po sel再次的到say666,此时bt查看函数调用栈,CFforwardingprep_0 -> forwarding_prep_0 -> [NSObject methodSignatureForSelector]
第一次调用resolveInstanceMethod,是走的慢速查找的动态决议。
第二次调用resolveInstanceMethod的堆栈信息,由底层系统库CoreFoundation调起,在消息转发完成以后再次开启慢速查找流程,进入动态方法决议又调用一次resolveInstanceMethod,所以总共是两次。
通过hopper查看coreFundation库,forwarding是一个回调,系统给了一次机会,再调用lookUpImpOrForward

