目录
在OC中的实例对象调用一个方法称作发送消息
当向某个对象发送一条消息时,若该对象的方法列表以及它相应继承链上的方法列表都无法找该消息的方法实现时,则会触发消息转发机制。
比如有如下代码:
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
- (void)sendMessage:(NSString*)message;
@end
NS_ASSUME_NONNULL_END
Person *p = [[Person alloc]init];
[p sendMessage:@"hello"];
.h文件里面声明了一个方法,.m里面没有实现这个方法,而Person调用了这个没有实现的方法,就会触发消息转发机制。
Person对象调用sendMessage:方法,OC的底层实现其实是下面这样的
objc_msgSend([Person new],@selector(sendMessage:),@"hello");
没有实现的方法,我们调用了,消息转发机制就好比我们平时遇到不认识的字去查字典一样。sendMessage:方法去哪里找,objc_msgSend 去[Person new] 这本字典里面去找,偏旁部首就是@selector这个方法编号,去查找@"hello"这个字。
我们看看isa指针的查找过程:
通过当前对象的isa 指针找到class,在方法列表里面查找方法,如果有就直接实现,如果当前列表里面没有,就沿着继承树去找,通过方法编号和IMP的映射关系,找到方法实现;如果都没有,这个时候就进入消息转发机制。
消息转发的流程图:
1.动态方法解析
第一步:对象在收到无法解读的消息后,首先会调用+(BOOL)resolveInstanceMethod:(SEL)sel
或者+ (BOOL)resolveClassMethod:(SEL)sel, 询问是否有动态添加方法来进行处理,处理实例如下
+(BOOL)resolveInstanceMethod:(SEL)sel {
// 方法匹配
NSString *methodName = NSStringFromSelector(sel);
if ([methodName isEqualToString:@"sendMessage:"]) {
return class_addMethod(self, sel, (IMP)sendMessage, "v@:@");
}
return [super resolveInstanceMethod:sel];//如果没有就走继承树方法
}
动态方法解析,就是添加一个处理方法
void sendMessage(id self,SEL _cmd,NSString* message) {
NSLog(@"---%@",message);
}
v@:@ 这个的意思是 v 表示返回值是void类型,上面方法的参数id self,id类型用@表示,SEL 表示方法编号,用冒号:表示,我们自己传的参数NSString类型用@表示。(id self, SEL _cmd, 是默认的参数)
运行之后我们看到打印了hello这个消息结果
2.快速转发
如果运行时在消息转发的第一步中未找到所调用方法的实现,那么当前接收者还有第二次机会进行处理。这时运行期系统会调用 -(id)forwardingTargetForSelector:(SEL)aSelector 方法,该方法可以返回一个能处理的对象,运行时系统会根据返回的对象进行查找,若找到则跳转到相应方法的实现,则消息转发结束。
快速转发说白了就是,找一个接受者
-(id)forwardingTargetForSelector:(SEL)aSelector {
NSString *methodName = NSStringFromSelector(aSelector);
if ([methodName isEqualToString:@"sendMessage:"]) {
return [SpareWheel new];//备用接受者
}
return [super forwardingTargetForSelector:aSelector];//走继承树方法
}
我们找的接受者的.m里面已经实现了sendMessage:方法
#import "SpareWheel.h"
@implementation SpareWheel
- (void)sendMessage:(NSString*)message {
NSLog(@"---SpareWheel say %@",message);
}
@end
运行结果如下:
3.慢速转发
- 方法签名
- 消息转发
#pragma mark - 方法签名
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSString *methodName = NSStringFromSelector(aSelector);
if ([methodName isEqualToString:@"sendMessage:"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
上面的方法签名的信息会保存在NSInvocation中,通过 SEL sel = [anInvocation selector] 获取方法编号
//这个方法是找一个 处理者
-(void)forwardInvocation:(NSInvocation *)anInvocation {
//消息转发
SEL sel = [anInvocation selector];
SpareWheel *tempObjc = [SpareWheel new];// 找一个处理者
if ([tempObjc respondsToSelector:sel]) {
// 如果这个处理者响应了这个方法,就指定它作为目标对象,处理当前的方法
[anInvocation invokeWithTarget:tempObjc];
}else{
[super forwardInvocation:anInvocation];
}
}
运行结果如下:
如果以上方法都没有实现,会执行-(void)doesNotRecognizeSelector:(SEL)aSelector 这个方法,为了防止程序崩溃,可以这么写,增加程序的健壮性。
//找不到方法就执行这个
-(void)doesNotRecognizeSelector:(SEL)aSelector {
NSLog(@"找不到方法");
}