iOS消息转发
消息转发解决NSNull取值崩溃
在OC中是通过 [person eat]
调用方法的。 他的底层实现是objc_msgSend(void /* id , self, SEL op, ... */ )
。
objc_msgSend需要动态查找自己要调用哪个方法, 会根据@selector的名字动态查找对应的方法。
我们利用[person eat]进行举例, 列出方法查找的过程
- 在person对象的缓存方法列表中(class的cache)查找要调用的方法(eat);
- 缓存中没有找到,则就去对象的方法列表中找;
- 如果还没找到,就去person的父类中执行 1 和 2两步
- 直到NSObject类都没查找到,就会进入消息转发流程;
以下开始消息转发的流程
- 执行
+ (BOOL)resolveInstanceMethod:(SEL)sel
, .动态方法解析,可以为person动态添加一个方法, 防止程序崩溃; - 执行
- (id)forwardingTargetForSelector:(SEL)aSelector
,快速消息转发,让其他对象处理这个函数 - 执行
- (void)forwardInvocation:(NSInvocation *)anInvocation
, 标准消息转发。调用forwardInvocation
之前, 会执行获取方法签名方法- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
, 如果返回值非空则通过forwardInvocation:转发消息, 返回值为空则向当前对象发送doesNotRecognizeSelector:消息,程序崩溃退出。 - 如果以上都没有进行处理,就会调用
- (void)doesNotRecognizeSelector:(SEL)aSelector
, 程序崩溃。
利用一张神图解释消息转发的流程
以下是具体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;
我的另外一篇文章 消息转发实际运用