《目录-iOS & OpenGL & OpenGL ES & Metal》
runtime的消息查找阶段已经探索完了,当都不满足条件,会进入消息转发阶段
前言
runtime的消息转发分为3步:
- 动态方法解析
- 对象方法解析
- 类方法解析
- 快速转发
- 慢速转发
- 方法签名
- 消息转发
一、动态方法解析
根据上篇文章,我们看了lookUpImpOrForward
这个方法,在最后都不满足的情况下,会调用resolveMethod_locked
这个方法
1、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
-
cls如果是元类,说明当前是类方法,调用
resolveClassMethod
。走完如果没找到,还会走一遍resolveInstanceMethod
方法
-
- 最后再调用
lookUpImpOrForward
方法,返回imp
2、对象方法解析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
-
然后再通过
lookUpImpOrNil
检查一遍,拿到我们添加的imp
-
最后返回到
lookUpImpOrForward
方法,重新循环去找,最终返回imp
2、类方法解析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
来实现消息转发
- 第一步,调用