objc_msgSend之动态解析和消息转发
前一个博客介绍了objc_msgSend消息发送的机制,本节我们从动态解析以及之后的消息转发来继续了解。
一、动态解析
我们通过上面的forward_imp来深入了解一下,我们通过对forward_imp一层层的调用关系最后定位到了最后这张图片,即当找不到方法实现的时候,就尝试一次方法解析。一次动态解析可以将方法加入到cache中。
我们继续向下看resolveMethod_locked的实现:
这里对cls是否是元类进行了判断,如果是元类,说明是类方法的调用,则调用resolveClassMethod,如果是类,则说明是实例方法的调用,则调用resolveInstanceMethod。
我们可以看到在resolveClassMethod、resolveInstanceMethod通过objc_msgSend去调用resolveClassMethod: 和resolveInstanceMethod:而resolveClassMethod其中的cls本来就是元类,通过getMaybeUnrealizedNonMetaClass来获取元类对应的类对象,然后再通过类对象发送消息(相当于类方法)。
那么此时的resolveInstanceMethod: 和 resolveClassMethod: 的实现就交给了开发者,可以利用这两个函数来防止程序找不到方法时所导致的奔溃。// 第一根稻草,使用动态解析,动态添加方法
@implementation LGPerson +(BOOL)resolveInstanceMethod:(SEL)sel{ NSLog(@"%s--%@",__func__,NSStringFromSelector(sel)); //动态实现添加test方法
//该方法中将test
的实现动态替换为other,所以我们查找到other方法,调用方法,结束查找并将方法缓存到cache中。
if(sel == @selector(test)){ IMP imp = class_getMethodImplementation(self.class, @selector(method1)); } return YES; } -(void) method1{ NSLog(@"%s",__func__); }
这里借助这篇博客(https://juejin.cn/post/7095673959537967135/)里的一张图来理解一下动态解析的流程:
二、消息转发流程
如果动态解析阶段还是没有对应的方法,那么就会来到消息转发阶段。消息转发阶段分为两部分,替换消息接收者阶段,也有叫快速转发,和完全消息转发阶段,也叫慢速转发阶段。
2.1 消息快速转发
实现forwardingTargetForSelector
方法,并返回替换的对象。实现如下:消息转发会去LGTeacher里面去找method1
。
//第二根稻草,使用快速消息转发,找其他对象来实现方法 - (id)forwardingTargetForSelector:(SEL)aSelector { if (aSelector == @selector(method1)) { return [LGTeacher new]
}
return nil; }
@implementation LGTeacher
//从消息转发而来 - (void)method1 { NSLog(@"%s__ %@",__FUNCTION__,[self class]); } @end
2.2 消息慢速转发
如果forwardingTargetForSelector
方法返回的是nil,那么我们还有最后一根稻草可以抓住-完全消息转发。相比于快速抓发,不经可以替换消息接受者,还能替换方法。
//消息的慢速转发 -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{ NSLog(@"%s",__func__); //方法的有效签名 return [NSMethodSignature signatureWithObjCTypes:"v@:"]; } -(void) forwardInvocation:(NSInvocation *)anInvocation{ LGTeacher *t = [LGTeacher new]; if([self respondsToSelector:anInvocation.selector]){ [anInvocation invokeWithTarget:self]; }else if([t respondsToSelector:anInvocation.selector]){ [anInvocation invokeWithTarget:t]; }else{ NSLog(@"该功能正在开发中"); } }
三、总结
3.1 动态解析和消息转发流程总结
- 在消息发送的时候,如果在
cache
和方法列表
中都没有找到方法实现,会来到动态解析阶段 - 通过调用
resolveInstanseMethod
或resolveClassMethod
,来动态的解析方法的实现。 - 如果在方法里返回YES,则会进入消息转发阶段,通过
frowardingTargetSelector
,可以将消息转发到能处理消息的实例里面。 - 如果没有进行快速转发,可以
methodSignatureForSeletor
和forwardInvocation
来进行完全消息转发。 - 注意:resolveInstanseMethod会被调用两次,第一次是快速转发的时候调用,第二次是慢速转发的时候调用m
ethodSignatureForSeletor时,其内部调用了class_getInstanceMethod方法,在这个方法的内部又调用了lookUpImpOrForward,此时消息的动态决议就会被调用两次。
3.1.1 类的实例方法动态方法解析:
2.类查找/父类查找
3.动态方法解析
resolveMethod_locked
,再去找cls的元类的resolveInstacenMethod
的imp
4.如果还是没有实现,再去找根元类,直到找到
NSObject根元类
的 resolveInstacenMethod
的imp
5.系统主动给
类对象
发送一条resolveInstacenMethod
消息(找到cls
的元类继承链中的resolveInstacenMethod
并调用它,并缓存到本类的cache
)(这是为了补救imp
没有找到的错误)6.接着还会往
类对象
里cache
通过sel
查找一次resolveInstacenMethod
的imp
2.元类/根元类的方法列表,没有找到
3.动态方法解析
resolveMethod_locked
,再去找cls
的元类的resolveClassMethod的imp4.如果还是没有实现,就去找
元类->根元类
直到找到NSObjec根元类
的resolveInstacenMethod
的imp
5.给元类发送一条消息(这里苹果做了处理,下面做源码分析),让
sel+imp
绑定3.2 苹果设计这两个阶段的原理
unrecognized selector sent to instance
类型的crash,这三种方式也被称做消息发送的三根救命稻草。。这些阶段的前提都是在没有找到方法实现,设计这些并不会影响消息发送的效率。而每根稻草实际的实现原理是不同的,动态解析是动态的去添加方法实现,消息转发是让一个替代者来实现。