前言
前面已经分别对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
方法抛出异常就可以处理所有的消息转发而避免闪退了 - 这种方法在不关心执行结果,只想避免闪退时会很有用