(三)RunTime消息转发机制

325 阅读4分钟

报错信息:unrecognized selector sent to instance 0x600001d24690

上一篇中我们提到MMPerson中声明了-(void)sendMessage;`方法,却没有实现该方法,在外部调用时就会抛出异常:

-[MMPerson sendMessage]: unrecognized selector sent to instance 0x600001d24690

那我们如何去规避这样的情况呢? 下面先上一张图,相信大家都见过,消息的转发流程:

消息的转发流程

  • (一)动态方法解析

+ (BOOL)resolveClassMethod:(SEL)sel    类方法解析
+ (BOOL)resolveInstanceMethod:(SEL)sel 实例方法解析

当运用消息转发运行时,根据调用的方法类型调用这两个方法其中一个,返回值BOOL类型,告诉系统该消息是否被处理,YES处理 NO未处理.

void sendMsg(id self, SEL _cmd){
    NSLog(@"1.动态方法解析");
}
//1.实例对象动态方法解析
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    
    NSString *methodName = NSStringFromSelector(sel);
    if ([methodName isEqualToString:@"sendMessage"]) {
        return class_addMethod([MMPerson class], sel, (IMP)sendMsg, "v@:");
    }
    return [super resolveInstanceMethod:sel];
}
void sendClassMsg(id self, SEL _cmd){
    NSLog(@"1.类动态方法解析");
}
//1.类方法的动态解析
+(BOOL)resolveClassMethod:(SEL)sel{
    NSString *methodName = NSStringFromSelector(sel);

    //类方法存在元类中,实例方法存在当前类
    if ([methodName isEqualToString:@"testClassMethod"]) {
        return class_addMethod(object_getClass([MMPerson class]) , sel, (IMP)sendClassMsg, "v@:");
    }
    return [super resolveClassMethod:sel];
}
因为当前类的方法存在metaClass中,所以调用object_getClass;

输出:动态方法解析

当接受者接受到的消息方法并没有找到的情况下,系统会调用该函数,给予这个对象一次动态添加该消息方法实现的机会,如果该对象动态的添加了这个方法的实现,就返回YES,告诉系统这个消息我已经处理完毕。再次运行该方法。

注意: 当这个对象在实现了resolveInstanceMethod,resolveClassMethod两个方法,并没有对该对象消息进行处理,那么该方法会被调用两次:一次是没有找到该方法需要对象解析处理;第二次是告诉系统我处理完成需要再次调用该方法但实际上并没有处理完成,所以会调用第二次该方法崩溃。

  • (二)快速转发(找备用接收者)

- (id)forwardingTargetForSelector:(SEL)aSelector

如果第一次没有处理,苹果会再给我们一次机会,找一个备用的类去实现这个方法,我们来创建一个备用的类MMReservePerson去实现sendMessage这个方法。

//2.快速转发(找备用接收者)
- (id)forwardingTargetForSelector:(SEL)aSelector{
    NSString *methodName = NSStringFromSelector(aSelector);
    if ([methodName isEqualToString:@"sendMessage"]) {
        return [MMReservePerson new];
    }
    return [super forwardingTargetForSelector:aSelector];
}

MMReservePerson类中的实现

- (void)sendMessage{
    NSLog(@"备用接受者%@",NSStringFromSelector(_cmd));
}

输出:

MMReservePerson.m:(15): 备用接受者sendMessage

我们也可以在备用接收者中MMReservePerson使用消息转发流程,同样适用。

如果前两次都没有进行处理,就会需要一个完整的方法实现。

  • (三)标准转发

需要同时调用两个函数

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
- (void)forwardInvocation:(NSInvocation *)anInvocation
  • 将未知SEL作为参数传入methodSignatureForSelector,把方法信息包装在NSInvocation,让后续forwardInvocation来进行处理
//3.标准转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSString *methodName = NSStringFromSelector(aSelector);
    if ([methodName isEqualToString:@"sendMessage"]) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

//消息集散地
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"selector = %@",NSStringFromSelector([anInvocation selector]));
}
输出:MMPerson.m:(63): selector = sendMessage

其中ObjCTypes参考文档Objective-C type encodings

forwardInvocation中我们可以不实现或者按照我们的需求实现一些方法。

  • (四)抛出异常

如果前面都没有处理就会抛出异常调用

//4.抛出异常
- (void)doesNotRecognizeSelector:(SEL)aSelector{
    NSLog(@"未找到该方法-%@",NSStringFromSelector(aSelector));
}

实现了这个方法虽然不会抛出异常unrecognized selector sent to instance 0x600001d24690但是会发生错误 EXC_BAD_INSTRUCTION

  • (五)NSObject 分类

根据SEL过程中isa的走向可以,最后会来到根类NSObject,所以在NSObject中实现是否可以,我们来验证一下 创建一个NSObject+Test的分类,实现类方法和实例方法

#import "NSObject+Test.h"

@implementation NSObject (Test)
+ (void)testClassMethod{
    NSLog(@"testClassMethod");
}
- (void)sendMessage{
    NSLog(@"%@",NSStringFromSelector(_cmd));
}

@end

输出:

NSObject+Test.m:(16): sendMessage
NSObject+Test.m:(13): testClassMethod

答案是可行的,因为instance(isa) -> object'class(superclass) -> object'superclass(superclass) -> root'class(NSObject)

总结

以上就是消息转发机制的四个步骤:

1.动态解析

2.快速转发(找备用接收者)

3.标准转发

4.抛出异常