iOS 方法动态决议和消息转发机制
- 1. 动态方法决议
- 1.1 实例方法动态解析 _class_resolveInstanceMethod
- 1.2 _class_resolveClassMethod
- 小结
- 2.1 快速转发流程
- 2.2 慢速转发流程
通过上一篇章的学习,OC调用方法,底层是调用 objc_msgSend 发送消息。在发送消息时会经过一系列的快速查找、慢速查找,如果查找到对应的 IMP,直接返回;如果没有找到,就会进入到方法的动态解析和消息转发流程。
1. 动态方法决议
通过探索
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
中的处理有所不同
元 类:说明是对元类中的类方法进行处理,但是元类中的方法是在根元类中以实例方法的形式存储的,所以最终会查找根元类的实例方法,调用实例方法解析查找。非元类:对储存在类中的实例方法进行处理。
1.1 实例方法动态解析 _class_resolveInstanceMethod
在
_class_resolveInstanceMethod
方法中对实例方法动态解析,源码如下:
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst){// 1. 判断系统是否实现SEL_resolveInstanceMethod方法// 即+(BOOL)resolveInstanceMethod:(SEL)sel,// 继承自NSObject的类,默认实现,返回NOif (! 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. 再次寻找IMPIMP 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收集。
1.2 _class_resolveClassMethod
如果是元类,在
_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的类,默认实现,返回NOif (! 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. 再次查找IMPIMP 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
进行动态解析。
2. 消息转发
在方法查找过程中,经过缓存查找,方法列表查找和动态方法解析,如果以上步骤都没有查找到
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 stretbeq __objc_msgForwardb __objc_msgForward_stretEND_ENTRY __objc_msgForward_impcacheENTRY __objc_msgForward// Non-stret versionMI_GET_EXTERN(r12, __objc_forward_handler)ldr r12, [r12]bx r12END_ENTRY __objc_msgForwardENTRY __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
一系列方法,进行消息转发。
2.1 快速转发流程
通过查看
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];}
2.2 慢速转发流程
进入慢速查找流程,首先必须先实现
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
- 点赞1
- 收藏
- 分享
- 文章举报
亮亮不想说话发布了18 篇原创文章 · 获赞 10 · 访问量 264私信关注