前言
前面已经分别对objc_msgSend的快速查找和 慢速查找 进行了分析,得出的结论是给一个对象发送消息,会先查找缓存,缓存找不到就会进入慢速查找流程,当慢速查找流程还是没能找到 imp,则会进入 动态方法决议流程 ,今天就开始对 动态方法决议和消息转发进行探索分析。
前文已经分析过,如果父类缓存返回的是forward_imp或者父类全部遍历完成后还是没有找到对应的 imp则会先执行一次如下逻辑
一、动态方法决议
1.动态方法决议相关源码分析
1.1动态方法决议入口:
//方法未查找到时会进入一次动态方法决议
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
1.2 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
resolveClassMethod(inst, sel, cls);
// 然后判断缓存中是否已经有了相应的imp,如果没有,再执行一次resolveInstanceMethod
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
// 动态方法决议可能已经存入缓存,尝试使用
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
- 判断
cls是否是元类元类。如果不是元类,代表要查找的是实例方法,执行resolveInstanceMethod - 如果是元类,代表要查找的是类方法,先执行
resolveClassMethod, 执行一次lookUpImpOrNilTryCache执行快速查找和慢速查找流程,判断是否能否查询到imp,如果还是没有查询到,则执行resolveInstanceMethod
1.3resolveInstanceMethod函数解析:
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
//动态方法决议需要实现的方法
SEL resolve_sel = @selector(resolveInstanceMethod:);
// 在元类中查找resolve_sel 对应的imp 。NSObject默认有实现 +resolveInstanceMethod 方法针对 NSObject及其子类此处针对的主要目的是将+resolveInstanceMethod方法添加到元类的缓存中,对非NSObject及其子类(如:NSProxy的子类)如果不能找到,直接返回
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
// Resolver not implemented.
return;
}
// 有实现此方法则发送消息调用+resolveInstanceMethod
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
// 如果动态方法决议处理了,将动态方法决议得到的mp缓存到cls的cache中
IMP imp = lookUpImpOrNilTryCache(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方法,对于NSObject及其子类,有默认实现+resolveInstanceMethod,此处的主要作用是将此方法添加到元类的缓存中,对于非NSObject及其子类(如:NSProxy的子类)如果没有实现则会直接返回。 - 发消息调用
+resolveInstanceMethod,如果动态方法决议给sel提供了imp,会将sel与imp一同存入方法列表。 lookUpImpOrNilTryCache如果有实现动态方法决议,执行此函数,将方法缓存到cls的cache中
1.3 resolveClassMethod函数解析
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
// 在元类中查找@selector(resolveClassMethod:) 对应的imp 。NSObject默认有实现 +resolveClassMethod 方法针对 NSObject及其子类此处针对的主要目的是将+resolveClassMethod方法添加到元类的缓存中,对非NSObject及其子类(如:NSProxy的子类)如果不能找到,直接返回
if (!lookUpImpOrNilTryCache(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 = lookUpImpOrNilTryCache(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));
}
}
}
- 判断元类中是否有
+resolveClassMethod方法,对于NSObject及其子类,有默认实现+resolveClassMethod,此处的主要作用是将此方法添加到元类的缓存中,对于非NSObject及其子类(如:NSProxy的子类)如果没有实现则会直接返回。 - 发消息调用
+resolveClassMethod,如果动态方法决议给sel提供了imp,会将sel与imp一同存入方法列表。 lookUpImpOrNilTryCache如果有实现动态方法决议,执行此函数,将方法缓存到cls的cache中
1.4lookUpImpOrNilTryCache函数解析
extern IMP lookUpImpOrNilTryCache(id obj, SEL, Class cls, int behavior = 0);
IMP lookUpImpOrNilTryCache(id inst, SEL sel, Class cls, int behavior)
{
return _lookUpImpTryCache(inst, sel, cls, behavior | LOOKUP_NIL);
}
behavior默认值为0,resolveInstanceMethod和resolveClassMethod函数都使用默认值0 | LOOKUP_NIL=4,所以当lookUpImpOrForward查询到imp为forward_imp时会返回nil
1.5lookUpImpOrForwardTryCache函数解析
extern IMP lookUpImpOrForwardTryCache(id obj, SEL, Class cls, int behavior = 0);
IMP lookUpImpOrForwardTryCache(id inst, SEL sel, Class cls, int behavior)
{
return _lookUpImpTryCache(inst, sel, cls, behavior);
}
behavior默认值为0,resolveMethod_locked函数传入的参数是3或11^2之后的值1或者9&LOOKUP_NIL都为0,所以当lookUpImpOrForward查询到imp为forward_imp时不会返回nil
1.7 _lookUpImpTryCache函数解析
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertUnlocked();
//判断cls是否初始化
if (slowpath(!cls->isInitialized())) {
// see comment in lookUpImpOrForward
return lookUpImpOrForward(inst, sel, cls, behavior);
}
// 查找缓存,如果找到缓存执行done流程
IMP imp = cache_getImp(cls, sel);
if (imp != NULL) goto done;
#if CONFIG_USE_PREOPT_CACHES
// 判断是否支持共享缓存
if (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) { //在共享缓存中查找imp
imp = cache_getImp(cls->cache.preoptFallbackClass(), sel);
}
#endif
// 缓存未找到imp,执行慢速查找
if (slowpath(imp == NULL)) {
return lookUpImpOrForward(inst, sel, cls, behavior);
}
done:
if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
return nil;
}
return imp;
}
cache_getImp在缓存中查找imp,如果找到imp,跳转到done流程- 缓存中未找到则判断是否支持共享缓存,支持查找共享缓存
- 缓存未找到imp,执行慢速查找
2.动态方法决议示例:
2.1实例方法的动态方法决议案例
声明一个XQPerson类如下:
@interface XQPerson : NSObject
-(void)eatTrepang;
@end
@implementation XQPerson
+(BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%@ --------- %s",NSStringFromSelector(sel),__func__);
return [super resolveInstanceMethod:sel];
}
@end
如上面代码所示,声明了一个eatTrepang(吃海参)方法,并没有实现然后在main方法中实例化 XQPerson后调用
int main(int argc, const char * argv[]) {
@autoreleasepool {
XQPerson* person = [[XQPerson alloc]init];
[person eatTrepang];
}
return 0;
}
执行结果如下:
如我们所料的确实崩溃了,但是为什么会执行两次呢?我们可以分析出第一次执行是在慢速查找流程时触发,那么第二次是在什么时候触发的呢?
通过打印堆栈信息,可以确定,第一次调用确实是在慢速查找时触发
由上面的打印分析可知,第二次动态方法决议是在消息转发结束后触发
2.2解决方案
上面的问题是声明了一个eatTrepang却没有实现,就像一个人,想吃海参,但是却吃不起,怎么办呢?
我们像如下代码,给XQPerson类添加一个eatRice方法,吃不起海参,可以吃饭嘛:
@implementation XQPerson
+(BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%@ --------- %s",NSStringFromSelector(sel),__func__);
if (sel == @selector(eatTrepang)) {
IMP imp = class_getMethodImplementation(self, @selector(eatRice));
Method method = class_getInstanceMethod(self, @selector(eatRice));
const char *types = method_getTypeEncoding(method);
class_addMethod(self, sel, imp, types);
}
return [super resolveInstanceMethod:sel];
}
-(void)eatRice{
NSLog(@"%s",__func__);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
XQPerson* person = [[XQPerson alloc]init];
[person eatTrepang];
[person eatTrepang];
}
return 0;
}
- 在
+resolveInstanceMethod方法,将获取到的imp添加到了方法列表resolveInstanceMethod函数调用lookUpImpOrNilTryCache时已经将eatTrepang方法添加到了缓存,resolveMethod_locked调用lookUpImpOrForwardTryCache时,返回了获取到的缓存的imp,此时返回imp方法查找结束,崩溃解决 - 因为方法已经添加到缓存和方法列表,所以调用了两次
eatTrepang方法,只执行了一次动态方法决议,
2.2 类方法案例分析:
2.2.1将上面的方法改成 类方法,如下所示:
@interface XQPerson : NSObject
+(void)eatTrepang;
@end
@implementation XQPerson
+(BOOL)resolveClassMethod:(SEL)sel{
NSLog(@"%@ --------- %s",NSStringFromSelector(sel),__func__);
return [super resolveClassMethod:sel];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
[XQPerson eatTrepang];
}
return 0;
}
执行结果依然是执行两次+resolveClassMethod后闪退,调用两次的逻辑和+resolveInstanceMethod是类似的
2.2.2解决方案
@implementation XQPerson
+(BOOL)resolveClassMethod:(SEL)sel{
NSLog(@"%@ --------- %s",NSStringFromSelector(sel),__func__);
if (sel == @selector(eatTrepang)) {
IMP imp = class_getMethodImplementation(object_getClass(self), @selector(eatRice));
Method method = class_getClassMethod(self, @selector(eatRice));
const char *types = method_getTypeEncoding(method);
class_addMethod(object_getClass(self), sel, imp, types);
}
return [super resolveClassMethod:sel];
}
+(void)eatRice{
NSLog(@"%s",__func__);
}
@end
- 分析流程和
+resolveInstanceMethod也是类似的,只是此时方法是被添加到了元类的方法列表
上文已经分析过,当 resolveClassMethod函数未能解决问题时,会调用一次resolveInstanceMethod函数,那么是否说明,类方法我们只需要实现+resolveInstanceMethod方法,并提供实现就可以解决问题了呢?接下来对代码做如下修改并执行:
@implementation XQPerson
+(BOOL)resolveClassMethod:(SEL)sel{
NSLog(@"%@ --------- %s",NSStringFromSelector(sel),__func__);
return [super resolveClassMethod:sel];
}
+(BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%@ --------- %s",NSStringFromSelector(sel),__func__);
return [super resolveInstanceMethod:sel];
}
@end
执行结果:
由上面的执行结果,可以得出,并没有执行+resolveInstanceMethod方法。这是为什么呢?
再次回顾一下resolveInstanceMethod函数:
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
//调用类方法时,当前cls是元类,给元类发送消息,首先在元类的元类即根元类查找,然后在根元类的父类根类(NSObject)查找
bool resolved = msg(cls, resolve_sel, sel);
IMP imp = lookUpImpOrNilTryCache(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));
}
}
}
- 调用类方法时,当前cls是元类,给元类发送消息,首先在元类的元类即根元类
NSObject(Meta)查找,然后在根元类的父类 根类(NSObject)查找,所以在XQPerson类实现+resolveInstanceMethod方法并不会执行。
由上面的分析可以推断出,当XQPerson未实现+resolveClassMethod方法或者实现方法后未添加对应的 imp会执行根类(NSObject)的+resolveInstanceMethod方法。接下来,给XQPerson添加一个分类:
@interface NSObject(XQ)
@end
@implementation NSObject(XQ)
+(BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%@ --------- %s",NSStringFromSelector(sel),__func__);
return NO;
}
@end
执行结果和推断的一样,确实首先调用了XQPerson的+resolveClassMethod,然后调用了NSObject(XQ)的+resolveInstanceMethod方法
由此可以得出结论:类方法和实例方法动态方法决议都会执行到NSObject的+resolveInstanceMethod方法,所以我们可以在+resolveInstanceMethod对动态方法决议进行整合:
@interface XQPerson : NSObject
+(void)eatTrepang;
-(void)eatTrepang;
@end
@implementation XQPerson
+(BOOL)resolveClassMethod:(SEL)sel{
if (sel == @selector(eatTrepang)) {
NSLog(@"%@ --------- %s",NSStringFromSelector(sel),__func__);
}
return [super resolveClassMethod:sel];
}
@end
@interface NSObject(XQ)
@end
@implementation NSObject(XQ)
+(BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(eatTrepang)) {
NSLog(@"%@ --------- %s",NSStringFromSelector(sel),__func__);
IMP imp = imp_implementationWithBlock(^{
NSLog(@"eatRice");
});
const char *types = "v@0:8";
class_addMethod(self, sel, imp, types);
}
return NO;
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
[XQPerson eatTrepang];
XQPerson* person = [[XQPerson alloc]init];
[person eatTrepang];
}
return 0;
}
如上示例,XQPerson类声明了+(void)eatTrepang和-(void)eatTrepang两个方法然后分别调用,通过执行结果可以看到,动态方法决议执行到NSObject(XQ)的+resolveInstanceMethod,并都添加了合适的imp,闪退都解决了。
动态方法决议流程图
二、消息转发
如果在动态方法决议后,依然没有返回合适的imp,程序将会进行什么操作呢,接下来我们继续对此进行分析
在分析方法的慢速查找时,提到过如下代码:
// 指定消息转发的imp
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
指定了_objc_msgForward_impcache为消息转发时的 imp,全局搜索_objc_msgForward_impcache,在objc-msg-arm64.s文件找到如下代码:
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
__objc_msgForward_impcache直接跳转__objc_msgForward- 拿到
__objc_forward_handler执行
全局搜索_objc_forward_handler,可以在objc-runtime.mm文件搜索到如下代码:
!__OBJC2__的部分可以忽略。_objc_forward_handler默认赋值为objc_defaultForwardHandler。objc_setForwardHandler函数里对_objc_forward_handler进行赋值
objc_defaultForwardHandler函数里可以看到非常熟悉的一段代码:unrecognized selector sent to instance...,我们第一时间会想到这里可能就是我们在找的函数,但是当我们在这个函数里打断点后,发现并不会执行,而当在objc_setForwardHandler打断点,发现在此处重新赋值为 CoreFoundation框架的_CF_forwarding_prep_0
此时当我们过掉断点,毫无悬念,程序会闪退:
由于CoreFoundation并没有完全开源,我们在其中不能找到相关的代码,那么消息转发的流程该怎么探索呢?
我们可以使用日志辅助调试来达到目的。
日志辅助调试
函数调用日志记录是在logMessageSend函数进行记录,全局搜索logMessageSend函数,此函数在log_and_fill_cache函数调用,需要满足objcMsgLogEnabled为true,全局搜索objcMsgLogEnabled,发现默认为false,所以我们需要对其进行赋值来修改。
void instrumentObjcMessageSends(BOOL flag)
{
bool enable = flag;
// Shortcut NOP
if (objcMsgLogEnabled == enable)
return;
// If enabling, flush all method caches so we get some traces
if (enable)
_objc_flush_caches(Nil);
// Sync our log file
if (objcMsgLogFD != -1)
fsync (objcMsgLogFD);
objcMsgLogEnabled = enable;
}
在instrumentObjcMessageSends函数有对其进行赋值,所以我们只需要在需要记录日志之前赋值为YES,需要关闭时赋值为NO,如下所示:
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
instrumentObjcMessageSends(YES);
[XQPerson eatTrepang];
instrumentObjcMessageSends(NO);
}
return 0;
}
通过查看logMessageSend的如下源码,我们可以定位到日志保存的路径为:/tmp/msgSends-xxx,
运行以上代码,然后右击 finder 前往文件夹输入/tmp/msgSends选择对应文件并打开如下:
由上面的日志,我们可以确定,类方法动态方法决议后依次执行+forwardingTargetForSelector -> +methodSignatureForSelector
对实例方法也进行依次测试,代码如下:
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
instrumentObjcMessageSends(YES);
XQPerson* person = [[XQPerson alloc]init];
[person eatTrepang];
instrumentObjcMessageSends(NO);
}
return 0;
}
日志如下:
由上面的日志,我们可以确定,实例方法动态方法决议后依次执行-forwardingTargetForSelector -> -methodSignatureForSelector
由此可以看出,实例方法和类方法的消息转发过程是类似的。
快速转发流程
我们首先分析forwardingTargetForSelector方法,在源码里搜索,可以在NSObject.mm查询到如下结果:
这两个方法都返回nil,查看官方文档如下:
由文档可知道,这里需要返回一个 非空 且不为 self的对象作为新的消息接受者,如果新对象依然不能处理将会到父类查找
快速转发案例:
对之前的代码做如下修改:
@interface MensFootBall : NSObject
@end
@implementation MensFootBall
-(void)eatTrepang{
NSLog(@"%s",__func__);
}
+(void)eatTrepang{
NSLog(@"%s",__func__);
}
@end
@interface XQPerson : NSObject
-(void)eatTrepang;
+(void)eatTrepang;
@end
@implementation XQPerson
-(id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(eatTrepang)) {
return [[MensFootBall alloc]init];
}
return [super forwardingTargetForSelector:aSelector];
}
+ (id)forwardingTargetForSelector:(SEL)sel {
if (sel == @selector(eatTrepang)) {
return [MensFootBall class];
}
return [super forwardingTargetForSelector:sel];
}
@end
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
instrumentObjcMessageSends(YES);
[XQPerson eatTrepang];
XQPerson* person = [[XQPerson alloc]init];
[person eatTrepang];
instrumentObjcMessageSends(NO);
}
return 0;
}
*****************************执行结果如下*****************************
2022-02-21 18:06:42.593261+0800 消息转发[38238:631847] +[MensFootBall eatTrepang]
2022-02-21 18:06:42.595062+0800 消息转发[38238:631847] -[MensFootBall eatTrepang]
Program ended with exit code: 0
由打印结果可以看到,之前不能执行的 +eatTrepang 和-eatTrepang都被转发给了MensFootBall并成功执行了,咱普通人吃不起海参,国足吃得起呀!
慢速转发
如果 快速转发 流程没有实现,或者快速转发指定的消息接受者依然不能处理该任务,则会进入 慢速转发 流程。
打开官方文档,搜索methodSignatureForSelector,结果如下:
由文档可知,需要和forwardInvocation配合使用,methodSignatureForSelector需要返回一个方法签名,forwardInvocation处理消息
接下来,对XQPerson做如下修改:
@implementation XQPerson
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(eatTrepang)) {
NSLog(@"%s ---- %@",__func__,NSStringFromSelector(aSelector));
NSMethodSignature* sign = [NSMethodSignature signatureWithObjCTypes:"v@:"];
return sign;
}
return [super methodSignatureForSelector:aSelector];
}
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(eatTrepang)) {
NSLog(@"%s ---- %@",__func__,NSStringFromSelector(aSelector));
NSMethodSignature* sign = [NSMethodSignature signatureWithObjCTypes:"v@:"];
return sign;
}
return [super methodSignatureForSelector:aSelector];
}
-(void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"%@------%@",anInvocation.target,NSStringFromSelector(anInvocation.selector));
if (anInvocation.selector == @selector(eatTrepang)) {
[anInvocation invokeWithTarget:[MensFootBall new]];
}
}
+(void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"%@------%@",anInvocation.target,NSStringFromSelector(anInvocation.selector));
if (anInvocation.selector == @selector(eatTrepang)) {
[anInvocation invokeWithTarget:MensFootBall.class];
}
}
******************************打印结果******************************
2022-02-22 10:20:39.004341+0800 消息转发[8367:191657] +[XQPerson methodSignatureForSelector:] ---- eatTrepang
2022-02-22 10:20:39.006389+0800 消息转发[8367:191657] +[XQPerson forwardInvocation:]------------XQPerson------eatTrepang
2022-02-22 10:20:39.007095+0800 消息转发[8367:191657] +[MensFootBall eatTrepang]
2022-02-22 10:20:39.008037+0800 消息转发[8367:191657] -[XQPerson methodSignatureForSelector:] ---- eatTrepang
2022-02-22 10:20:39.009661+0800 消息转发[8367:191657] -[XQPerson forwardInvocation:]------------<XQPerson: 0x10a004200>------eatTrepang
2022-02-22 10:20:39.010352+0800 消息转发[8367:191657] -[MensFootBall eatTrepang]
-methodSignatureForSelector和+methodSignatureForSelector分别返回实例方法和类方法的方法签名-forwardInvocation和+forwardInvocation分别处理类方法的转发- 如上面的结果,实例和类方法被成功转发了,闪退解决了
对所有消息进行转发
上面的方法都需要指定一个对象接收消息进行处理,那么有没有办法处理所有的消息转发呢?
@interface XQSafe : NSObject
@end
@implementation XQSafe
@end
@interface XQPerson : NSObject
-(void)eatTrepang;
+(void)eatTrepang;
@end
@implementation XQPerson
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSMethodSignature* sign = [NSMethodSignature signatureWithObjCTypes:"v@:"];
return sign;
}
-(void)forwardInvocation:(NSInvocation *)anInvocation
{
@try {
[anInvocation invokeWithTarget:[XQSafe new]];
} @catch (NSException *exception) {
NSLog(@"%@ ------%@------发生了异常",anInvocation.target,NSStringFromSelector(anInvocation.selector));
}
@finally {
}
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
XQPerson* person = [[XQPerson alloc]init];
[person eatTrepang];
[person performSelector:@selector(eatRice:) withObject:@(1)];
}
return 0;
}
***************************执行结果******************************
2022-02-22 11:26:16.371396+0800 消息转发[13131:276987] -[XQSafe eatTrepang]: unrecognized selector sent to instance 0x1098a9e60
2022-02-22 11:26:18.839312+0800 消息转发[13131:276987] <XQSafe: 0x1098a9e60> ------eatTrepang------发生了异常
2022-02-22 11:26:18.839508+0800 消息转发[13131:276987] -[XQSafe eatRice]: unrecognized selector sent to instance 0x109d04440
2022-02-22 11:26:19.673050+0800 消息转发[13131:276987] <XQSafe: 0x109d04440> ------eatRice:------发生了异常
Program ended with exit code: 0
- 在
-methodSignatureForSelector方法返回一个方法签名后,在-forwardInvocation方法抛出异常就可以处理所有的消息转发而避免闪退了 - 这种方法在不关心执行结果,只想避免闪退时会很有用