iOS消息转发

消息转发解决NSNull取值崩溃

在OC中是通过 [person eat]调用方法的。 他的底层实现是objc_msgSend(void /* id , self, SEL op, ... */ )

objc_msgSend需要动态查找自己要调用哪个方法, 会根据@selector的名字动态查找对应的方法。

我们利用[person eat]进行举例, 列出方法查找的过程

  1. 在person对象的缓存方法列表中(class的cache)查找要调用的方法(eat);
  2. 缓存中没有找到,则就去对象的方法列表中找;
  3. 如果还没找到,就去person的父类中执行 1 和 2两步
  4. 直到NSObject类都没查找到,就会进入消息转发流程;

以下开始消息转发的流程

  1. 执行+ (BOOL)resolveInstanceMethod:(SEL)sel, .动态方法解析,可以为person动态添加一个方法, 防止程序崩溃;
  2. 执行- (id)forwardingTargetForSelector:(SEL)aSelector,快速消息转发,让其他对象处理这个函数
  3. 执行- (void)forwardInvocation:(NSInvocation *)anInvocation, 标准消息转发。调用forwardInvocation之前, 会执行获取方法签名方法 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector, 如果返回值非空则通过forwardInvocation:转发消息, 返回值为空则向当前对象发送doesNotRecognizeSelector:消息,程序崩溃退出。
  4. 如果以上都没有进行处理,就会调用- (void)doesNotRecognizeSelector:(SEL)aSelector, 程序崩溃。

利用一张神图解释消息转发的流程

ios 事件 转发 ios消息转发解决崩溃_消息转发

以下是具体demo进行解释

demo github地址, 如果对你有帮助给个star呗

→ person对象调用一个不存在的方法testMethod, 会执行resolveInstanceMethod, 此方法内部可. 以class_addMethod一个动态方法, 动态添加成功, 消息转发提前结束;

→如果没有class_addMethod则会进入forwardingTargetForSelector, 这里面可以把方法转发给
MessageForwardAnimal进行行处理, 如果MessageForwardAnimal类有testMethod方法, 则消息转发结束;

→ 如果MessageForwardAnimal类没有testMethod方法, 则进入标准消息转发

→ 先调用methodSignatureForSelector, 获取testMethod的方法签名, 如果能获取到签名, 则调用forwardInvocation进行标准转发, 否则程序崩溃, 调用doesNotRecognizeSelector

MessageForwardVC.m
//
//  MessageForwardVC.m
//  进阶学习demo
//
//  Created by nyl on 2019/8/6.
//  Copyright © 2019 nieyinlong. All rights reserved.
//

#import "MessageForwardVC.h"
#import <objc/message.h>

#import "MessageForwardPerson.h"
#import "MessageForwardAnimal.h"

@interface MessageForwardVC ()

@end

@implementation MessageForwardVC

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor whiteColor];
    
    MessageForwardPerson *person = [MessageForwardPerson new];
    [self performSelector:@selector(testMethod)];
}


+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSLog(@"%s", __FUNCTION__);
    if (sel == @selector(testMethod)) {
        // 动态添加方法(dynamicMethod), 拯救了崩溃
        // (第1次拯救)
          class_addMethod([self class], sel, (IMP)dynamicMethod, "v@:@");  // 打印  拯救了崩溃 dynamicMethod
    }
    return [super resolveInstanceMethod:sel];
}

- (id)forwardingTargetForSelector:(SEL)aSelector { // 快速消息转发, 指定备用接收者
    NSLog(@"%s", __FUNCTION__);
    if (aSelector == @selector(testMethod)) {
        // (第2次拯救)
        // return [MessageForwardAnimal new]; // MessageForwardAnimal.h 中没有声明testMethod方法, 但是.m中有testMethod方法即可
        // 打印  ?(MessageForwardAnimal) 我来实现, -[MessageForwardAnimal testMethod]
    }
    return [super forwardingTargetForSelector:aSelector];
}

//- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
//    NSLog(@"%s", __FUNCTION__);
//    if (aSelector == @selector(testMethod)) {
//        NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];
//        if (!methodSignature) {
//            if ([MessageForwardPerson instancesRespondToSelector:aSelector]) {
//                // (第3次拯救), 3次拯救失败就调用 doesNotRecognizeSelector:
//                methodSignature = [MessageForwardPerson instanceMethodSignatureForSelector:aSelector];
//            }
//            return methodSignature;
//        }
//    }
//    return [super methodSignatureForSelector:aSelector];
//}

// 同上
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSLog(@"%s", __FUNCTION__);
    if (aSelector == @selector(testMethod)) {
        // 手动生成签名
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}


// 拿到消息转发签名
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"%s", __FUNCTION__);
    if (anInvocation.selector == @selector(testMethod)) {
        // 正常转发, MessageForwardAnimal来实现
//        MessageForwardPerson *object = [MessageForwardPerson new];
        MessageForwardAnimal *object = [MessageForwardAnimal new];
        if ([object respondsToSelector:anInvocation.selector]) {
			[anInvocation invokeWithTarget:object]; //  ?(MessageForwardAnimal) 我来实现, -[MessageForwardAnimal testMethod]
        }
    }
}

- (void)doesNotRecognizeSelector:(SEL)aSelector{
    NSString *selStr = NSStringFromSelector(aSelector);
    NSLog(@"%@不存在",selStr);
}



#pragma mark -

void dynamicMethod(id self, SEL _cmd) {
    NSLog(@"拯救了崩溃 %s", __FUNCTION__);
}

@end
MessageForwardAnimal.m
#import "MessageForwardAnimal.h"

@implementation MessageForwardAnimal

- (void)testMethod {
    NSLog(@"?(MessageForwardAnimal) 我来实现, %s", __FUNCTION__);
}

@end
MessageForwardPerson.m
#import "MessageForwardPerson.h"

@implementation MessageForwardPerson

@end

ps: MessageForwardAnimal 没有声明testMethod;

我的另外一篇文章 消息转发实际运用