主要内容:objc_msgSend
的快速查找
和慢速查找
,都没有找到方法时,苹果给了挽救的机会
:
一、动态方法决议
1.对象方法
2.类方法
二、消息转发
1.快速消息转发
2.慢速消息转发
一、动态方法决议
动态方法决议
:慢速查找流程未找到方法,会给一次机会,重新查询方法
在objc_msgSend流程—慢速查找 中慢速查找
流程未找到
方法实现时,会执行一次动态方法决议``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);
}
流程图
image.png
1.对象方法
对象方法
在快速查找
-> 慢速查找
都没有找到
的情况下,会走到 resolveInstanceMethod
方法,源码如下:
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
// look的是 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));
}
}
}
主要步骤
- 在
发送resolveInstanceMethod消息
前,需要查找cls类
中是否有该方法的实现
,即通过lookUpImpOrNil方法
又会进入lookUpImpOrForward
慢速查找流程查找resolveInstanceMethod
方法- 如果没有,则直接返回
- 如果有,则发送
resolveInstanceMethod
消息
- 再次慢速查找实例方法的实现,即通过
lookUpImpOrNil
方法又会进入lookUpImpOrForward
慢速查找流程查找实例方法
代码
- main
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
[person ins_say666];
}
return 0;
}
- LGPerson
@interface LGPerson : NSObject
- (void) ins_say666;
- (void)ins_sayMaster;
+ (void) cls_sayHappy;
@end
@implementation LGPerson
- (void)ins_sayMaster{
NSLog(@"ins_sayMaster - %s", __func__);
}
+ (void) cls_sayHappy{
NSLog(@"cls_sayHappy");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%@ 来了",NSStringFromSelector(sel));
return [super resolveInstanceMethod:sel];
}
打印结果:
image.png执行到了LGPerson
中的resolveInstanceMethod
方法,还执行了两次,而且程序还是崩溃了,通过堆栈信息可以看出
- 【第一次动态决议】第一次的“来了”是在查找
ins_say666
方法时会进入动态方法决议
- 【第二次动态决议】第二次“来了”是在慢速转发流程中调用了>
CoreFoundation
框架中的NSObject(NSObject) methodSignatureForSelector:
后,会再次进入动态决议
继续修改
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%@ 来了",NSStringFromSelector(sel));
if (sel == @selector(ins_say666)) {
//获取ins_sayMaster方法的imp
IMP imp = class_getMethodImplementation(self, @selector(ins_sayMaster));
//获取ins_sayMaster的方法签名
Method sayMMethod = class_getInstanceMethod(self, @selector(ins_sayMaster));
const char *type = method_getTypeEncoding(sayMMethod);
//将sel的实现指向ins_sayMaster
return class_addMethod(self, sel, imp, type);
}
return [super resolveInstanceMethod:sel];
}
打印结果:
image.png
方法重定向后,不再崩溃。
2.类方法
对类方法,通过重写resolveClassMethod
类方法来解决方法未找到的崩溃问题,即在LGPerson类中重写该方法,并将cls_sayNB类方法的实现指向类方法cls_sayHappy
+ (BOOL)resolveClassMethod:(SEL)sel {
NSLog(@"%@ 来了",NSStringFromSelector(sel));
if (sel == @selector(cls_sayNB)) {
IMP imp = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(cls_sayHappy));
Method sayMHappy = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(cls_sayHappy));
const char *type = method_getTypeEncoding(sayMHappy);
return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type);
}
return [super resolveClassMethod:sel];
}
resolveClassMethod
类方法的重写需要注意一点,传入的cls
不再是类
,而是元类
,可以通过objc_getMetaClass方法
获取类的元类
,原因是因为类方法
在元类
中是实例方法
优化
上面的这种方式是单独在每个类中重写,有没有更好的?其实通过方法慢速查找流程可以发现查找路径有两条
- 实例方法:
类 -> 父类 -> 根类 -> nil
- 类方法:
元类 -> 根元类 -> 根类 -> nil
共同点是如果前面没有找到,都会来到 根类即 NSObject中
查找。所以我们可以通过 NSObject添加分类
的方式来实现统一处理
。类方法在查找到元类和根类时,实际查找的也是对象方法,所以 对象方法和类方法都放在resolveInstanceMethod
中处理
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%@ 来了",NSStringFromSelector(sel));
if (sel == @selector(ins_say666)) {
IMP imp = class_getMethodImplementation(self, @selector(ins_sayMaster));
Method sayMMaster = class_getInstanceMethod(self, @selector(ins_sayMaster));
const char *type = method_getTypeEncoding(sayMMaster);
return class_addMethod(self, sel, imp, type);
} else if (sel == @selector(cls_sayNB)) {
IMP imp = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(cls_sayHappy));
Method sayMHappy = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(cls_sayHappy));
const char *type = method_getTypeEncoding(sayMHappy);
return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type);
}
return NO;
}
上面这种写法还是会有其他的问题,比如系统方法
也会被更改,针对这一点,是可以优化的,即我们可以针对自定义类中方法统一方法名的前缀
,根据前缀来判断是否是自定义方法,然后统一处理自定义方法
,例如可以在崩溃前pop到首页,主要是用于app线上防崩溃的处理,提升用户的体验。也可以收集崩溃信息传到后台,再进行相应处理。
二、消息转发
消息转发
:如果动态方法决议仍然没有找到实现,则进行消息转发
但我们找遍了源码也没有发现消息转发的相关源码,可以通过instrumentObjcMessageSends
方式打印发送消息日志
- 通过
lookUpImpOrForward -> log_and_fill_cache -> logMessageSend
,在logMessageSend
源码下方找到instrumentObjcMessageSends
的源码实现,所以,在main中调用instrumentObjcMessageSends
打印方法调用的日志信息,有以下两点准备工作- 1、打开
objcMsgLogEnabled
开关,即调用instrumentObjcMessageSends
方法时,传入YES
- 2、在
main
中通过extern
声明instrumentObjcMessageSends
方法
- 1、打开
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
instrumentObjcMessageSends(YES);
[person ins_say666];
instrumentObjcMessageSends(NO);
}
return 0;
}
- 运行代码,前往
/tmp/msgSends
目录,发现有msgSends
开头的日志文件,打开日志文件会发现:
image.png
1.快速消息转发
// 快速转发
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
// return [super forwardingTargetForSelector:aSelector];
//将消息的接收者指定为LGTeacher,在LGTeacher中查找ins_say666的实现
return [LGTeacher alloc];
}
打印结果:
image.png2.慢速消息转发
有快速一般就有慢速,以下是慢速转发流程
如果forwardingTargetForSelector
也没有找到,那就找
methodSignatureForSelector
方法,使用时会搭配 forwardInvocation,
就会实现慢速转发流程
// 慢速转发流程
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
// 返回方法签名
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s - %@",__func__,anInvocation);
anInvocation.target = [LGTeacher alloc];
[anInvocation invoke];
}
打印结果
image.png
anInvocation
里存的信息:消息接收者、消息名称、消息签名
等- 在
forwardInvocation
方法中,不修改anInvocation
也不会崩溃,是因为 GM 认为就是不处理这个方法了,那就谁愿意处理谁处理,把这个方法流出去,像漂流瓶一样慢速
与快速
的区别
,快速
直接指定一个对象
就OK了,慢速
中targe
t可以改,sel
也可以改,可以保存之后想什么时候invoke
就什么时候invoke, 可以给一个统一的全局的方法
。慢速转发流程,拥有的权力更大
,更加灵活
总结
消息流程:快速查找 -> 慢速查找 -> 动态方法决议 -> 消息转发(快速转发 + 慢速转发)
网友评论