动态方法决议&消息转发

前言

 当消息进行快速慢速查找后,依然没找到,也没进行处理时,会报错unrecognized selector,在报错前,如果进行了适当的处理,也许不会报错。

方法未实现报错

🌰

@interface SLPerson : NSObject
- (void)say1;
- (void)say2;
+ (void)say3;
@end

@implementation SLPerson
- (void)say1{
    NSLog(@"SLPerson say : %s",__func__);
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        SLPerson * p = [[SLPerson alloc]init];
        [p say2];
        [SLPerson say3];
    }
    return 0;
}

运行代码,会出现以下的报错 

2023-02-09 17:34:03.505342+0800 KCObjcBuild[4308:96734] -[SLPerson say2]: unrecognized selector sent to instance 0x600000008030
2023-02-09 17:34:03.512201+0800 KCObjcBuild[4308:96734] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[SLPerson say2]: unrecognized selector sent to instance 0x600000008030'
2023-02-09 17:40:17.821639+0800 KCObjcBuild[4455:102062] +[SLPerson say3]: unrecognized selector sent to class 0x100008158
2023-02-09 17:40:17.822475+0800 KCObjcBuild[4455:102062] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[SLPerson say3]: unrecognized selector sent to class 0x100008158'

由慢速查找源码得知,最后报错是走__objc_msgForward_impcache

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_forward_handler,发现本质是调用objc_defaultForwardHandler

// Default forward handler halts the process.
__attribute__((noreturn, cold)) void
objc_defaultForwardHandler(id self, SEL sel)
{
    _objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
                "(no message forward handler is installed)", 
                class_isMetaClass(object_getClass(self)) ? '+' : '-', 
                object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;

三次方法查找的挽救机会

  1. 动态方法决议
  2. 快速转发
  3. 慢速转发

动态方法决议

动态方法决议源码实现如下:

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

流程图(转载自 Style_月月,感谢🙏)如下:

 当类没实现实例方法say2时,可以通过以下方式进行补救

- (void)say2_1{
    NSLog(@"SLPerson say : %s",__func__);
}

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(say2)) {
        NSLog(@"%@ 来了", NSStringFromSelector(sel));
        //获取sayMaster方法的imp
        IMP imp = class_getMethodImplementation(self, @selector(say2_1));
        //获取sayMaster的实例方法
        Method sayMethod  = class_getInstanceMethod(self, @selector(say2_1));
        //获取sayMaster的丰富签名
        const char *type = method_getTypeEncoding(sayMethod);
        //将sel的实现指向sayMaster
        return class_addMethod(self, sel, imp, type);
    }
    
    return [super resolveInstanceMethod:sel];
}

当类没实现类方法say3时,可以通过以下方式进行补救 

+(void)say3_1{
    NSLog(@"SLPerson say : %s",__func__);
}

+ (BOOL)resolveClassMethod:(SEL)sel{
    
    if (sel == @selector(say3)) {
        NSLog(@"%@ 来了", NSStringFromSelector(sel));
        
        IMP imp = class_getMethodImplementation(objc_getMetaClass("SLPerson"), @selector(say3_1));
        Method slClassMethod  = class_getInstanceMethod(objc_getMetaClass("SLPerson"), @selector(say3_1));
        const char *type = method_getTypeEncoding(slClassMethod);
        return class_addMethod(objc_getMetaClass("SLPerson"), sel, imp, type);
    }
    
    return [super resolveClassMethod:sel];
}

优化

由方法慢速查找的路径可知,实例方法(类-父类-根类-nil)、类方法(元类-根元类-根类-nil),c路径都会经过根类(即NSObject),所以可以直接在NSObject分类进行处理

#import "NSObject+test.h"

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(say2)) {
        NSLog(@"%@ 来了", NSStringFromSelector(sel));
        //获取sayMaster方法的imp
        IMP imp = class_getMethodImplementation(self, @selector(say2_1));
        //获取sayMaster的实例方法
        Method sayMethod  = class_getInstanceMethod(self, @selector(say2_1));
        //获取sayMaster的丰富签名
        const char *type = method_getTypeEncoding(sayMethod);
        //将sel的实现指向sayMaster
        return class_addMethod(self, sel, imp, type);
    } else if (sel == @selector(say3)) {
        NSLog(@"%@ 来了", NSStringFromSelector(sel));

        IMP imp = class_getMethodImplementation(objc_getMetaClass("SLPerson"), @selector(say3_1));
        Method slClassMethod  = class_getInstanceMethod(objc_getMetaClass("SLPerson"), @selector(say3_1));
        const char *type = method_getTypeEncoding(slClassMethod);
        return class_addMethod(objc_getMetaClass("SLPerson"), sel, imp, type);
    }
    
    return NO;
}

消息转发流程

消息转发分为

1.快速转发 :forwardingTargetForSelector

2.慢速转发:methodSignatureForSelector、forwardInvocation

 快速转发

SLPerson类重写forwardingTargetForSelector,将消息接收者指定为SLStudent,SLStudent中有say具体实现

🌰代码

@interface SLStudent : NSObject
- (void)say;
@end
@implementation SLStudent
- (void)say{
    NSLog(@"SLStudent say : %s",__func__);
}
@end

@interface SLPerson : NSObject
- (void)say;
@end
@implementation SLPerson
- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
    //将消息的接收者为SLStudent的一个实例
    return [[SLStudent alloc]init];
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        SLPerson * p = [[SLPerson alloc]init];
        [p say];
    }
    return 0;
}
2023-02-10 16:35:31.349392+0800 KCObjcBuild[18227:300068] -[SLPerson forwardingTargetForSelector:] - say
2023-02-10 16:35:31.350415+0800 KCObjcBuild[18227:300068] SLStudent say : -[SLStudent say] 

 慢速转发

🌰代码

即使不在forwardInvocation进行处理,也不会报错

@implementation SLPerson
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"%s - %@",__func__,anInvocation);
}
@end
2023-02-10 16:41:10.124284+0800 KCObjcBuild[18514:303965] -[SLPerson methodSignatureForSelector:] - say
2023-02-10 16:41:10.125004+0800 KCObjcBuild[18514:303965] -[SLPerson forwardInvocation:] - <NSInvocation: 0x104007d80>

 当然正常来说还是会对forwardInvocation进行处理的,即可完成消息慢速转发。需在SLPerson中定义一个SLStudent类型的成员,作为转发的target。

@interface SLPerson : NSObject {
    SLStudent * _student;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"%s - %@",__func__,anInvocation);
    anInvocation.target = _student;
    [anInvocation invoke];
}
2023-02-10 16:51:40.640554+0800 KCObjcBuild[18690:311853] -[SLPerson methodSignatureForSelector:] - say
2023-02-10 16:51:40.641265+0800 KCObjcBuild[18690:311853] -[SLPerson forwardInvocation:] - <NSInvocation: 0x100504400>
2023-02-10 16:51:40.641398+0800 KCObjcBuild[18690:311853] SLStudent say : -[SLStudent say]

执行两次动态方法决议

当走到消息快速转发这一步且forwardingTargetForSelector中返回nil的时候,发现整个流程动态决议方法总共会走两次。

- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
    return nil;
}

//- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
//    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
//    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
//}
//
//- (void)forwardInvocation:(NSInvocation *)anInvocation{
//    NSLog(@"%s - %@",__func__,anInvocation);
//    anInvocation.target = _student;
//    [anInvocation invoke];
//}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(say)) {
        NSLog(@"%s - %@ 来了",__func__, NSStringFromSelector(sel));
    }
    return [super resolveInstanceMethod:sel];
}
2023-02-10 17:19:01.941588+0800 KCObjcBuild[19122:329964] +[SLPerson resolveInstanceMethod:] - say 来了
2023-02-10 17:19:01.942366+0800 KCObjcBuild[19122:329964] -[SLPerson forwardingTargetForSelector:] - say
2023-02-10 17:19:01.942609+0800 KCObjcBuild[19122:329964] +[SLPerson resolveInstanceMethod:] - say 来了
2023-02-10 17:19:01.942782+0800 KCObjcBuild[19122:329964] -[SLPerson say]: unrecognized selector sent to instance 0x104006d30

总结

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值