之前讲到了消息传递机制,如果在整个类的继承体系中还是找不到与选择子相符的方法,也就是对象或者类对象收到了无法解读的消息,那么就会进入到消息转发环节。
在编译期,向对象或者类对象发送了其无法解读的消息并不会报错,因为在运行期可以继续向类和元类(metaClass)中添加方法,所以编译器在编译期还无法确定类中到底会不会有某个方法的实现。当对象接收到无法解读的消息后,就会启动“消息转发(message forwarding)”机制,我们可以在消息转发过程中告诉对象应该如处理未知消息。
消息转发分为两个阶段。第一阶段叫做“动态方法解析(dynamic method resolution)”,或者叫“动态方法决议”。第二阶段涉及到“完整的消息转发机制(full forwarding mechanism)”,或者叫“完整的消息转发原理”。
1 动态方法绑定
动态方法绑定的意思就是,征询消息接受者所属的类,看其是否能动态添加方法,以处理当前“这个未知的选择子(unknown selector)“。实例对象在接受到无法解读的消息后,首先会调用其所属类的下列类方法:
+ (BOOL)resolveInstanceMethod:(SEL)selector
类对象在接受到无法解读的消息后,那么运行期系统就会调用另外的一个方法,如下:
+ (BOOL)resolveClassMethod:(SEL)selector
1.1 动态方法绑定实例
//.h文件
#import <Foundation/Foundation.h>
@interface Person : NSObject
-(void)run;
@end
//.m文件
#import "Person.h"
#import <objc/message.h>
@implementation Person
//-(void)run{
// NSLog(@"哥么跑起来了");
//}
//动态方法绑定--对象方法
+(BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"sel = %@",NSStringFromSelector(sel));
if (sel == @selector(run)) {//如果方法为run方法
//我就动态添加一个方法
class_addMethod(self, sel, (IMP)newRun, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void newRun(id self , SEL sel){
NSLog(@"addMethod is runing");
}
@end
从代码可以看出,Person的.m文件中并未实现run方法。但添加了一个C语言的newRun方法,通过resolveInstanceMethod:当有调用run方法的时候动态绑定了newRun。这样是否可行呢?掉run方法的时候会不会走newRun。
测试一发,果然
那我们把run方法放开,会怎样呢?
会走run还是newrun,还是都走呢?
只走了run方法,看来resolveInstanceMethod:是只会在找不到方法实现才会走
如果运行期系统已经执行完了动态方法绑定,那么消息接受者自己就无法再以动态新增方法的形式来响应包含该未知选择子的消息了,此时就进入了第二阶段——消息重定向。运行期系统会请求消息接受者以其他手段来处理与消息相关的方法调用。
2 完整的消息转发机制(full forwarding mechanism)
完整的消息转发又分为两个阶段,第一阶段称为消息重定向(replacement receiver),第二阶段才是启动完整的消息转发机制。
2.1 消息重定向
当前接受者如果不能处理这条消息,运行期系统会请求当前接受者让其他接受者处理这条消息,与之对应的方法是:
- (id)forwardingTargetForSelector:(SEL)selector
方法参数代表未知的选择子,返回值为重定向的对象,若当前接受者能找到重定向,就直接返回,这个未知的选择子将会交由重定向处理。如果找不到重定向,就返回nil,此时就会启用"方法签名"。
2.1.1 消息重定向实例
创建PersonTwo,.h文件不用创建run,在.m实现run就可以了
#import "PersonTwo.h"
@implementation PersonTwo
-(void)run{
NSLog(@"personTwo is runing");
}
@end
把Person中resolveInstanceMethod中添加方法去掉
//动态方法绑定--对象方法
+(BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"sel = %@",NSStringFromSelector(sel));
// if (sel == @selector(run)) {//如果方法为run方法
// //我就动态添加一个方法
// class_addMethod(self, sel, (IMP)newRun, "v@:");
// return YES;
// }
return [super resolveInstanceMethod:sel];
}
//方法重定向
-(id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"sel = %@",NSStringFromSelector(aSelector));
//定向给PersonTwo
return [PersonTwo new];
// return [super forwardingTargetForSelector:aSelector];
}
重新运行
完美的转移到Person上
2.2 完整的消息转发机制
2.2.1 方法签名
如果转发算法已经来到了这一步,那么代表之前的所有转发尝试都失败了,此时进行方法签名。
签名是打标记,为了下一步的
//方法签名---
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSString *sel = NSStringFromSelector(aSelector);
if ([sel isEqualToString:@"run"]) {
//签名
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}else{
return [super methodSignatureForSelector:aSelector];
}
}
如果methodSignatureForSelector这里没有签名,回直接到第5步。有签名会到第4步,派发签名(转发方法)
2.2.2 拿到签名后派发签名
可以将方法转发给其他对象
//拿到方法签名后派发消息
-(void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"---------%@------------",anInvocation);
//取得消息
SEL selector = [anInvocation selector];
//转发消息
PersonTwo *ptwo = [PersonTwo new];
if ([ptwo respondsToSelector:selector]) {
// 将方法转发给PersonTwo
[anInvocation invokeWithTarget:ptwo];
}else{
return [super forwardInvocation:anInvocation];
}
}
测试一发,果然,PersonTwo的run方法执行了。
2.2.3 最后一步
当前面都失败后,走这个方法,
-(void)doesNotRecognizeSelector:(SEL)aSelector{
NSLog(@"此方法不存在");
}
以上就是完整的消息转发机制。是不是感觉以后可以利用这个做很多事情。例如做一个方法的懒加载。
提供一份Demo,希望对你有帮助
想了解更多RunTime,可以参考:
Runtime 消息传递机制
Runtime 交换方法
写在最后:
希望这篇文章对您有帮助,最好就是实操一边,这样才能理解更深入。
当然如果您发现有可以优化的地方,希望您能慷慨的提出来。
最后祝您工作愉快!
网友评论