runtime的消息查找阶段已经探索完了,当都不知足条件,会进入消息转发阶段app
runtime的消息转发分为3步:post
根据上篇文章,咱们看了lookUpImpOrForward
这个方法,在最后都不知足的状况下,会调用resolveMethod_locked
这个方法ui
resolveMethod_locked
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
resolveInstanceMethod(inst, sel, cls);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNil(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
复制代码
cls若是不是元类,说明当前是对象方法,调用resolveInstanceMethod
spa
cls若是是元类,说明当前是类方法,调用resolveClassMethod
。走完若是没找到,还会走一遍resolveInstanceMethod
方法3d
lookUpImpOrForward
方法,返回imp
resolveInstanceMethod
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
// 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. cls
IMP imp = lookUpImpOrNil(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));
}
}
}
复制代码
容错判断if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA()))
,是为了看当前类以及父类、元类等有没有实现resolveInstanceMethod:
,若是都没有就不必继续往下走了。(NSObject里面是默认有这个方法的)日志
若是实现了,就经过objc_msgSend
,向当前cls
发送消息,也就是调用resolveInstanceMethod:
这个已经实现的方法,在这个方法里,咱们已经手动给sel
添加了一个imp
code
而后再经过lookUpImpOrNil
检查一遍,拿到咱们添加的imp
orm
最后返回到lookUpImpOrForward
方法,从新循环去找,最终返回imp
对象
resolveClassMethod
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
if (!lookUpImpOrNil(inst, @selector(resolveClassMethod:), cls)) {
// Resolver not implemented.
return;
}
Class nonmeta;
{
mutex_locker_t lock(runtimeLock);
nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
// +initialize path should have realized nonmeta already
if (!nonmeta->isRealized()) {
_objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
nonmeta->nameForLogging(), nonmeta);
}
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(nonmeta, @selector(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
IMP imp = lookUpImpOrNil(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 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));
}
}
}
复制代码
和上面的容错同样
若是实现了,就经过objc_msgSend
,向当前cls的元类 ->
发送消息,调用resolveClassMethod
方法,给sel
添加imp
,跟上面流程同样
特别提醒:若是没有实现,还会走一次 对象方法解析 resolveInstanceMethod
,是由于有一种特殊状况,就是元类的父类是根元类,根元类的父类是NSObject,因此要在走一次这个方法,看NSObject里面有没有实现。
NSObject
里面的 resolveInstanceMethod
方法,但并不意味着 全部的防崩溃方法都要写在这里!缘由:+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%s ",__func__);
NSString *name = NSStringFromSelector(sel);
if ([name isEqual:@"sayHello"]) {
//若是是调用sayHello方法,就返回一个sayHappy的IMP
IMP newIMP = class_getMethodImplementation(self, @selector(sayHappy));
Method newMethod = class_getInstanceMethod(self, @selector(sayHappy));
const char *newType = method_getTypeEncoding(newMethod);
return class_addMethod(self, sel, newIMP, newType);
}
return [super resolveInstanceMethod:sel];
}
复制代码
若是咱们继续上面的思路走的话,会发现这个方法已经走完了,线索就断掉了。。。
其实在lookUpImpOrForward
里有一个log_and_fill_cache
方法,这个方法里面有一个logMessageSend
方法调用, 这一系列操做(方法就不一一展现了,太多了,直接到达目的地)中有一步骤是打印日志,而日志存储的位置:
snprintf (buf, sizeof(buf), "/tmp/msgSends-%d", (int) getpid **());**
复制代码
经过全局探索,发现是否打印的开关是这么一句代码:
instrumentObjcMessageSends(BOOL flag)
复制代码
因而,咱们跑一下代码看一下,(随便建立一份工程,源码跑不动)准备工做:
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *objc = [[LGPerson alloc] init];
//打开开关
instrumentObjcMessageSends(true);
//这个实例方法,只声明了,没有实现,跑起来确定会崩溃
[objc sayHello];
//关闭开关
instrumentObjcMessageSends(false);
}
return 0;
}
复制代码
咱们运行,找一下/tmp/msgSends
这个路径存储的日志文件看一下:
从日志中能够看出,动态方法解析以后调用了forwardingTargetForSelector
方法。咱们去官方看一下这段代码的含义:
大体意思就是,找一个备用的接收者,即返回一个实现了这个方法的对象。
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s ",__func__);
NSString *name = NSStringFromSelector(aSelector);
if ([name isEqual:@"sayHello"]) {
//Jack这个类 声明并实现了 sayHello这个方法
return [Jack alloc];
}
return [super forwardingTargetForSelector:aSelector];
}
复制代码
若是没有这个备用接收者,或者没有实现这个方法,就会走到慢速转发
在快速转发的官方介绍中,我有圈起来,若是不实现快速转发,会调用forwardInvocation:
在这方法,那咱们来看一下这个方法的官方介绍:
官方介绍中也圈起来了,要重写
forwardInvocation:
这个方法,就必须同时重写methodSignatureForSelector:
方法,咱们继续看一下官方介绍:
慢速流程流程就是先走methodSignatureForSelector
提供一个方法签名,而后走forwardInvocation
经过NSInvocation
来实现消息的转发
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s ",__func__);
NSString *name = NSStringFromSelector(aSelector);
if ([name isEqual:@"sayHello"]) {
//方法签名
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
//其实这个方法能够空着不写
-(void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s ",__func__);
SEL aSelector = [anInvocation selector];
if ([[Jack alloc] respondsToSelector:aSelector]){
[anInvocation invokeWithTarget:[Jack alloc]];
}else{
[super forwardInvocation:anInvocation];
}
}
复制代码
有这么一个状况,resolveInstanceMethod
调用了2次
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%s ",__func__);
return [super resolveInstanceMethod:sel];
}
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s ",__func__);
return [super forwardingTargetForSelector:aSelector];
}
//先拿到方法签名
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s ",__func__);
NSString *name = NSStringFromSelector(aSelector);
if ([name isEqual:@"sayHello"]) {
//方法签名 v-返回值,@-传入对象,:-传入sel
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
//经过anInvocation进行消息转发,也能够空着这个方法不做处理
-(void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s ",__func__);
}
复制代码
缘由: 在调用
methodSignatureForSelector
方法的时候,咱们传了一个type,这时候系统会去进行匹配,走到class_getInstanceMethod
这个方法里面:
Method class_getInstanceMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
// This deliberately avoids +initialize because it historically did so.
// This implementation is a bit weird because it's the only place that
// wants a Method instead of an IMP.
#warning fixme build and search caches
// Search method lists, try method resolver, etc.
lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);
#warning fixme build and search caches
return _class_getMethod(cls, sel);
}
复制代码
又会走到lookUpImpOrForward
方法,最终仍是进入resolveMethod_locked
这个方法,而后就会再走一次 resolveInstanceMethod
方法。
** 坑点总结**:若是出现走两次resolveInstanceMethod
方法的状况,是由于在签名以后,系统作了一些操做:会根据咱们传入的签名type进行匹配,调用class_getInstanceMethod
方法就会再走一次。
前提:整个查找流程没有找到该方法,而后进入消息转发流程
首先来到动态方法解析,添加一个方法实现(给sel
指定一个imp
)
+(BOOL)resolveInstanceMethod:(SEL)sel
方法+(BOOL)resolveClassMethod:(SEL)sel
方法快速转发,若是动态方法解析没有找到对应的处理方法,就会来到这里
- (id)forwardingTargetForSelector:(SEL)aSelector
方法,return一个备用接收者慢速转发,若是快速转发也没有处理,就会来到这里
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
方法拿到方法签名-(void)forwardInvocation:(NSInvocation *)anInvocation
经过NSInvocation
来实现消息转发