OC动态方法决议和消息转发

2,459 阅读15分钟

前言

前面已经分别对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,会将selimp一同存入方法列表。
  • lookUpImpOrNilTryCache 如果有实现动态方法决议,执行此函数,将方法缓存到 clscache

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,会将selimp一同存入方法列表。
  • lookUpImpOrNilTryCache 如果有实现动态方法决议,执行此函数,将方法缓存到 clscache

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,resolveInstanceMethodresolveClassMethod函数都使用默认值 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;
}

执行结果如下:

image.png

如我们所料的确实崩溃了,但是为什么会执行两次呢?我们可以分析出第一次执行是在慢速查找流程时触发,那么第二次是在什么时候触发的呢?

image.png

通过打印堆栈信息,可以确定,第一次调用确实是在慢速查找时触发

image.png

由上面的打印分析可知,第二次动态方法决议是在消息转发结束后触发

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;
}

image.png

  • +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;
}

image.png

执行结果依然是执行两次+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

image.png

  • 分析流程和+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

执行结果: image.png

由上面的执行结果,可以得出,并没有执行+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

image.png

执行结果和推断的一样,确实首先调用了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;
}

image.png

如上示例,XQPerson类声明了+(void)eatTrepang-(void)eatTrepang两个方法然后分别调用,通过执行结果可以看到,动态方法决议执行到NSObject(XQ)+resolveInstanceMethod,并都添加了合适的imp,闪退都解决了。

动态方法决议流程图

动态方法决议.drawio.png

二、消息转发

如果在动态方法决议后,依然没有返回合适的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文件搜索到如下代码:

image.png

  • !__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

image.png

此时当我们过掉断点,毫无悬念,程序会闪退:

image.png

由于CoreFoundation并没有完全开源,我们在其中不能找到相关的代码,那么消息转发的流程该怎么探索呢? 我们可以使用日志辅助调试来达到目的。

日志辅助调试

函数调用日志记录是在logMessageSend函数进行记录,全局搜索logMessageSend函数,此函数在log_and_fill_cache函数调用,需要满足objcMsgLogEnabledtrue,全局搜索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选择对应文件并打开如下:

image.png

由上面的日志,我们可以确定,类方法动态方法决议后依次执行+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;
}

日志如下:

image.png

由上面的日志,我们可以确定,实例方法动态方法决议后依次执行-forwardingTargetForSelector -> -methodSignatureForSelector

由此可以看出,实例方法和类方法的消息转发过程是类似的。

快速转发流程

我们首先分析forwardingTargetForSelector方法,在源码里搜索,可以在NSObject.mm查询到如下结果:

image.png

这两个方法都返回nil,查看官方文档如下:

image.png

由文档可知道,这里需要返回一个 非空 且不为 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,结果如下:

image.png

image.png

由文档可知,需要和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方法抛出异常就可以处理所有的消息转发而避免闪退了
  • 这种方法在不关心执行结果,只想避免闪退时会很有用