通过探索objc_msgSend
源码,当慢速查找依然没有找到IMP
时,会进入方法动态解析阶段,源码如下:
在经过_class_resolveMethod
方法后,在进行一次retry
,重新进行一遍方法的查找流程,而只有一次动态方法解析的机会就是在_class_resolveMethod
方法中。
_class_resolveMethod
源码如下:
void _class_resolveMethod(Class cls, SEL sel, id inst) { // 是否是元类 if (! cls->isMetaClass()) { // try [cls resolveInstanceMethod:sel] _class_resolveInstanceMethod(cls, sel, inst); } else { // try [nonMetaClass resolveClassMethod:sel] // and [cls resolveInstanceMethod:sel] _class_resolveClassMethod(cls, sel, inst); // 已经处理 if (!lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) { // 对象方法 决议 _class_resolveInstanceMethod(cls, sel, inst); } } } 复制代码 复制代码
因为类方法是存储在元类中,所以在_class_resolveMethod
中的处理有所不同
元 类:说明是对元类中的类方法进行处理,但是元类中的方法是在根元类中以实例方法的形式存储的,所以 最终会查找根元类的实例方法,调用实例方法解析查找。 非元类:对储存在类中的实例方法进行处理。 复制代码 复制代码
作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:413038000,不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!
在_class_resolveInstanceMethod
方法中对实例方法动态解析,源码如下:
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst) { // 1\. 判断系统是否实现SEL_resolveInstanceMethod方法 // 即+(BOOL)resolveInstanceMethod:(SEL)sel, // 继承自NSObject的类,默认实现,返回NO if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) { // Resolver not implemented. // 不是NSObject的子类,也未实现+(BOOL)resolveInstanceMethod:(SEL)sel, // 直接返回,没有动态解析的必要 return; } // 2\. 系统给你一次机会 - 你要不要针对 sel 来操作一下下 BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; bool resolved = msg(cls, SEL_resolveInstanceMethod, sel); // 3\. 再次寻找IMP IMP imp = lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/); 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,并添加到类中,实现方法动态解析,防止系统崩溃。
+ (BOOL)resolveInstanceMethod:(SEL)sel{ NSLog(@"来了老弟:%s - %@",__func__,NSStringFromSelector(sel)); if (sel == @selector(saySomething)) { NSLog(@"说话了"); IMP sayHIMP = class_getMethodImplementation(self, @selector(sayHello)); Method sayHMethod = class_getInstanceMethod(self, @selector(sayHello)); const char *sayHType = method_getTypeEncoding(sayHMethod); return class_addMethod(self, sel, sayHIMP, sayHType); } return [super resolveInstanceMethod:sel]; } 复制代码 复制代码
我们也可以在此方法中根据方法的前缀、路由、事务,跳转的不同的页面,进行bug收集。
如果是元类,在_class_resolveClassMethod
方法中对相关类方法的进行动态解析,该方法的实现步骤和实例方法的实现步骤类似,区别是消息发送的时候获取的是元类,即:
_class_resolveClassMethod
源码如下:
static void _class_resolveClassMethod(Class cls, SEL sel, id inst) { assert(cls->isMetaClass()); // 1\. 判断系统是否实现SEL_resolveClassMethod方法 // 即+(BOOL)resolveClassMethod:(SEL)sel, // 继承自NSObject的类,默认实现,返回NO if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) { // Resolver not implemented. return; } //2\. 系统给你一次机会 // 通过objc_msgSend调用一下+(BOOL)resolveClassMethod:(SEL)sel方法 BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; bool resolved = msg(_class_getNonMetaClass(cls, inst), SEL_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 //3\. 再次查找IMP IMP imp = lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/); 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进行动态方法解析:
+ (BOOL)resolveClassMethod:(SEL)sel{ NSLog(@"来了类方法:%s - %@",__func__,NSStringFromSelector(sel)); if (sel == @selector(sayLove)) { NSLog(@"说- 说你你爱我"); IMP sayHIMP = class_getMethodImplementation(self, @selector(sayObjc)); Method sayHMethod = class_getClassMethod(self, @selector(sayObjc)); const char *sayHType = method_getTypeEncoding(sayHMethod); // 类方法在元类 objc_getMetaClass("LGStudent") return class_addMethod(self, sel, sayHIMP, sayHType); } return [super resolveClassMethod:sel]; } 复制代码 复制代码
resolveInstanceMethod
方法resolveClassMethod
方法元类
继承自根元类
,根元类
最终继承自NSObject
,因此对类方法解析的时候,最终会查找到NSObject
。由于元类和根源类由系统创建,无法修改,所以可以再根元类的父类NSObject
中,添加对应的实例方法resolveInstanceMethod
进行动态解析。在方法查找过程中,经过缓存查找,方法列表查找和动态方法解析,如果以上步骤都没有查找到IMP
,也没有进行方法动态解析,那么就会进入最后一步,崩溃。
_objc_msgForward_impcache
是汇编方法,如下:
STATIC_ENTRY __objc_msgForward_impcache // Method cache version // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band Z is 0 (EQ) for normal, 1 (NE) for stret beq __objc_msgForward b __objc_msgForward_stret END_ENTRY __objc_msgForward_impcache ENTRY __objc_msgForward // Non-stret version MI_GET_EXTERN(r12, __objc_forward_handler) ldr r12, [r12] bx r12 END_ENTRY __objc_msgForward ENTRY __objc_msgForward_stret 复制代码 复制代码
在 _objc_msgForward_impcache
中,调用__objc_msgForward
,然后调用__objc_forward_handler
,转掉_objc_forward_handler OC
方法如下,然后就是经典崩溃。
那么,在崩溃时,为什么会打印如上图的一系列堆栈信息呢 ?
通过查看lookUpImpOrForward
源码,如上图,当查找到IMP
时,会调用log_and_fill_cache
方法,进行缓存填充和日志存储。
log_and_fill_cache
如上图,通过控制objcMsgLogEnabled
来控制日志存储,日志会记录在/tmp/msgSends
目录下,而objcMsgLogEnabled
的赋值是在instrumentObjcMessageSends
之中,可以暴露这个方法,来达到外部打日志的操作。
在查看/tmp/msgSends
目录下的文件,如图:
发现调用resolveInstanceMethod:
, forwardingTargetForSelector
,methodSignatureForSelector
,doesNotRecognizeSelector
一系列方法,进行消息转发。
通过查看forwardingTargetForSelector
的官方文档,
- (id)forwardingTargetForSelector:(SEL)aSelector
方法,
1\. 返回一个对象。如果这个对象非空、非nil,系统会将消息转发给这个对象执行,否则,继续查找其他流程。 系统给了将这个SEL转给其他对象的机会。 2\. 如果返回nil,或者没有处理消息转发,会走到forwardInvocation:方法进行处理,进入慢速消息转发流程。 复制代码 复制代码
可以通过一下代码,将saySomething方法
的消息转发到LGTeacher
类中实现,而不会引起系统崩溃,至此消息快速转发结束。
- (id)forwardingTargetForSelector:(SEL)aSelector{ NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector)); if (aSelector == @selector(saySomething)) { return [LGTeacher alloc]; } return [super forwardingTargetForSelector:aSelector]; } 复制代码 复制代码
进入慢速查找流程,首先必须先实现methodSignatureForSelector
方法,返回一个签名,这个方法签名里面封装了返回值类型,参数类型等信息。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{ NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector)); if (aSelector == @selector(saySomething)) { // v @ : return [NSMethodSignature signatureWithObjCTypes:"v@:"]; } return [super methodSignatureForSelector:aSelector]; } 复制代码 复制代码
然后还必须实现- (void)forwardInvocation:(NSInvocation *)anInvocation
;
- (void)forwardInvocation:(NSInvocation *)invocation { SEL aSelector = [invocation selector]; if ([friend respondsToSelector:aSelector]) [invocation invokeWithTarget:friend]; else [super forwardInvocation:invocation]; } 复制代码 复制代码
注意:
1\. forwardInvocation 方法和 methodSignatureForSelector 方法必须同时实现 2\. methodSignatureForSelector 会生成一个签名,NSInvocation对象,将NSInvocation对象作为 参数传给 forwardInvocation 方法的 3\. 在forwardInvocation方法里面将消息给能处理该消息的对象,以避免对象调用 didNotRecognizeSelector 方法导致崩溃 4\. forwardInvocation 这个方法类似于将消息当做事务堆放起来,在这里谁可以操作就在这里面操作, 就算不操作也不会崩溃,这里也是防崩溃的最后处理机会。 复制代码 复制代码
接下来看一下系统NSObject
中forwardInvocation
的实现:
+ (void)forwardInvocation:(NSInvocation *)invocation { [self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)]; } - (void)forwardInvocation:(NSInvocation *)invocation { [self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)]; } // Replaced by CF (throws an NSException) + (void)doesNotRecognizeSelector:(SEL)sel { _objc_fatal("+[%s %s]: unrecognized selector sent to instance %p", class_getName(self), sel_getName(sel), self); } // Replaced by CF (throws an NSException) - (void)doesNotRecognizeSelector:(SEL)sel { _objc_fatal("-[%s %s]: unrecognized selector sent to instance %p", object_getClassName(self), sel_getName(sel), self); } 复制代码 复制代码
由此可见,系统最后是在doesNotRecognizeSelector
方法中抛出异常的,所以重写forwardInvocation
方法后,不管里面有么有实现,或者执行父类的方法,程序都是不会崩溃的。
消息转发流程图:
当调用了未实现的方法,三个解决途径:
1、resolveInstanceMethod:为发送消息的对象的添加一个IMP,然后再让该对象去处理 2、forwardingTargetForSelector:将该消息转发给能处理该消息的对象 3、methodSignatureForSelector和 forwardInvocation:第一个方法生成方法签名,然后创建 NSInvocation 对象作为参数给第二个方法,然后在forwardInvocation 方法里面做消息处理, 只要在第二个方法里面不执行父类的方法,即使不处理也不会崩溃 复制代码 复制代码
关于resolveInstanceMethod
方法调用两次的问题?
IMP
时,没有找到时,进入方法动态解析时,会第一次调用resolveInstanceMethod
forwardingTargetForSelector
,将该消息转发给能处理该消息的对象methodSignatureForSelector
和 forwardInvocation
,返回签名forwardInvocation
在断点调试时,通过汇编,发现第二次resolveInstanceMethod
调用在第三步和第四步之间,猜测,当签名处理完毕时,会匹配返回的签名和原始调用方法的签名,那么怎么找到原始调用方法的签名呢?重新发送一次消息。调用class_getInstanceMethod
,重新查找一次方法,再一次发送resolveInstanceMethod
[图片上传中...(image-724fcb-1588230283775-0)]
作者:亮亮不想说话