通过上一章的学习,咱们了解到了方法是如何经历快速和慢速的流程进行查找的,若是通过方法的查找,能够找到对应IMP的话,则会直接返回,若是没有找到,就要进入本章的动态方法解析和消息转发流程了缓存
经过上一章,咱们基本了解了方法查找的3个阶段以下:bash
消息发送阶段:从类及父类的方法缓存列表及方法列表查找方法;post
动态解析阶段:若是消息发送阶段没有找到方法,则会进入动态解析阶段,负责动态的添加方法实现;ui
消息转发阶段:若是也没有实现动态解析方法,则会进行消息转发阶段,将消息转发给能够处理消息的接受者来处理;编码
经过阅读lookUpImpOrForward
源码,咱们知道动态方法解析,主要在_class_resolveMethod
中,消息转发,主要在_objc_msgForward_impcache
中,自此,咱们来逐个进行研究spa
在lookUpImpOrForward
方法中,通过对类,父类,元类的缓存和方法列表的查询后,仍旧没有找到方法,则会进入动态方法解析阶段,源码以下,咱们能够看到,在通过 _class_resolveMethod
后,会进行一遍retry
操做,从新进行一遍方法的查找流程,而且只有一次动态方法解析的机会 3d
_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_resolveInstanceMethod
该方法是进行实例方法动态解析的主要实现方法,咱们经过源码来逐行分析日志
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
️✅// 判断系统是否实现SEL_resolveInstanceMethod方法,即+(BOOL)resolveInstanceMethod:(SEL)sel
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
️✅// 若是没有找到(通常是不继承子NSObject的类),则直接返回
return;
}
️✅// 若是找到,则经过objc_msgSend调用一下+(BOOL)resolveInstanceMethod:(SEL)sel方法
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
// Cache the result (good or bad) so the resolver does not fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
️✅// 同时再次寻找方法的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)); } } } 复制代码
经过阅读源码,咱们知道,若是要进行方法的动态解析的话,则须要再系统方法code
+ (BOOL)resolveInstanceMethod:(SEL)sel
中进行处理,咱们须要经过对未实现的方法指定一个已经实现的方法的IMP,并添加到类中,实现方法动态解析,这样咱们就能够对一个未实现的方法进行动态解析了
+ (BOOL)resolveInstanceMethod:(SEL)sel{
️✅// 获取到须要动态解析的方法名
if (sel == @selector(saySomething)) {
NSLog(@"说话了");
️✅// 获取到须要动态解析到的方法sayHello的IMP和Method
IMP sayHIMP = class_getMethodImplementation(self, @selector(sayHello));
Method sayHMethod = class_getInstanceMethod(self, @selector(sayHello));
const char *sayHType = method_getTypeEncoding(sayHMethod);
️✅// 经过API添加方法
return class_addMethod(self, sel, sayHIMP, sayHType);
}
return [super resolveInstanceMethod:sel];
}
复制代码
_class_resolveClassMethod
若是是元类,则相关类方法的处理在_class_resolveClassMethod
方法中处理,该方法的实现步骤和实例方法的实现步骤相似,只不过是消息发送的时候获取的是元类
+ (BOOL)resolveClassMethod:(SEL)sel
进行动态方法解析
+ (BOOL)resolveClassMethod:(SEL)sel{
if (sel == @selector(sayLove)) {
️✅// 获取到元类中存储的类方法sayObjc
IMP sayHIMP = class_getMethodImplementation(objc_getMetaClass("LGStudent"), @selector(sayObjc));
Method sayHMethod = class_getClassMethod(objc_getMetaClass("LGStudent"), @selector(sayObjc));
const char *sayHType = method_getTypeEncoding(sayHMethod);
️✅// 将类方法实现添加在元类之中
return class_addMethod(objc_getMetaClass("LGStudent"), sel, sayHIMP, sayHType);
}
return [super resolveClassMethod:sel];
}
复制代码
resolveInstanceMethod
方法,并添加方法在类中resolveClassMethod
方法,并添加方法在元类中resolveInstanceMethod
进行动态解析在lookUpImpOrForward
方法中,通过对接受对象缓存,方法列表查找和动态方法解析后,若是以上步骤都没有进行处理,那么就会进入,消息处理的最后一步,即消息转发流程,这是补救方法崩溃最终的一步了,若是不想方法崩溃,那此时必定要处理了。
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
复制代码
_objc_msgForward_impcache
也是一段汇编的代码,经过代码咱们能够知道,汇编通过了一段__objc_msgForward
方法,发现里面只有一段崩溃的实现,可是根据崩溃信息,咱们能够发现中间还通过了___forwarding___
和_CF_forwarding_prep_0
等方法,可是是在CoreFoundation
库中的,因此消息转发的处理在此时进行的
log_and_fill_cache
,说明系统会对缓存的方法加日志,咱们能够经过系统的日志在查看方法调用的状况。经过方法咱们能够看到,日志会记录在
/tmp/msgSends
目录下,且经过
objcMsgLogEnabled
变量来控制是否存储日志
objcMsgLogEnabled
的赋值是在
instrumentObjcMessageSends
之中,因此咱们能够暴露这个方法,来达到外部打日志的操做
instrumentObjcMessageSends
方法,并定位在要崩溃的方法中,能够打上日志,来查看调用
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGStudent *student = [LGStudent alloc] ;
instrumentObjcMessageSends(true);
[student saySomething];
instrumentObjcMessageSends(false);
}
return 0;
}
复制代码
经过寻找/tmp/msgSends
文件,以下图所示,咱们发现通过了resolveInstanceMethod
,forwardingTargetForSelector
,methodSignatureForSelector
,doesNotRecognizeSelector
,这就是咱们要寻找的处理方法。
其中resolveInstanceMethod
为方法动态解析,doesNotRecognizeSelector
为通过上述的最后崩溃调用
因此,最终消息的转发就再forwardingTargetForSelector
和methodSignatureForSelector
中了,这也是一个快速
和慢速
两种流程
forwardingTargetForSelector
经过查看- (id)forwardingTargetForSelector:(SEL)aSelector
方法的文档,咱们可得
forwardInvocation:
方法进行处理objc_msgSend(forwardingTarget, sel, ...);
来实现消息的发送saySomething
方法的处理被转发到
LGTeacher
的相关类中实现。至此消息转发的快速流程结束,不会崩溃
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(saySomething)) {
return [LGTeacher alloc];
}
return [super forwardingTargetForSelector:aSelector];
}
复制代码
methodSignatureForSelector
若是没有通过上述的消息转发的快速流程,那么会进入一个消息转发的慢速流程之中,实现慢速流程。
首先必须实现methodSignatureForSelector
方法,经过下面文档的能够得出
而后实现methodSignatureForSelector
后,还必须实现- (void)forwardInvocation:(NSInvocation *)anInvocation;
方法进行处理,经过文档,咱们可得
forwardInvocation
和methodSignatureForSelector
必须是同时存在的,底层会经过方法签名,生成一个NSInvocation
,将其做为参数传递调用InInvocation
中编码的消息的对象。对于全部消息,此对象没必要相同。anInvocation
将消息发送到该对象。anInvocation
将保存结果,运行时系统将提取结果并将其传递给原始发送者。咱们能够看到NSInvocation
源码
- (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx;
-- 方法参数,所以在此方法里面,咱们能够决定将消息转发给谁(target),甚至还能够修改消息的参数,因为anInvocation
会存储消息selector
里面带来的参数,而且能够根据消息所对应的方法签名肯定消息参数的个数,因此咱们经过- (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx;能够对参数进行修改。总之你能够按照你的意愿,配置好anInvocation,而后执行[anInvocation invoke];便可完成消息的转发调用
- (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{
NSLog(@"%s ",__func__);
SEL aSelector = [anInvocation selector];
if ([[LGTeacher alloc] respondsToSelector:aSelector])
[anInvocation invokeWithTarget:[LGTeacher alloc]];
else
[super forwardInvocation:anInvocation];
}
复制代码
forwardingTargetForSelector
实现消息转发的快速流程,直接转发到能处理相关方法的对象中,而且方法要保持一致methodSignatureForSelector
提供一个方法签名,用来生成NSInvocation
参数进行后续的使用forwardInvocation
经过对NSInvocation
来实现消息的最终转发NSObject
中doesNotRecognizeSelector
方法,致使方法寻找不到,程序崩溃
MJ大神提供的相关消息转发C语言实现源码
int __forwarding__(void *frameStackPointer, int isStret) {
id receiver = *(id *)frameStackPointer;
SEL sel = *(SEL *)(frameStackPointer + 8);
const char *selName = sel_getName(sel);
Class receiverClass = object_getClass(receiver);
// 调用 forwardingTargetForSelector:
if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
id forwardingTarget = [receiver forwardingTargetForSelector:sel];
if (forwardingTarget && forwardingTarget != receiver) {
return objc_msgSend(forwardingTarget, sel, ...);
}
}
// 调用 methodSignatureForSelector 获取方法签名后再调用 forwardInvocation
if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
if (methodSignature && class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer];
[receiver forwardInvocation:invocation];
void *returnValue = NULL;
[invocation getReturnValue:&value];
return returnValue;
}
}
if (class_respondsToSelector(receiverClass,@selector(doesNotRecognizeSelector:))) {
[receiver doesNotRecognizeSelector:sel];
}
// The point of no return.
kill(getpid(), 9);
}
复制代码