对象在接收到未实现的消息时,会进行消息转发。
消息转发原理:
每个类都有一个methodlist,里面每个元素是指向method结构体的指针。每个Method结构体里面包含一个SEL和一个对应的IMP,消息转发就是将原本的SEL和IMP的这种对应关系分开,和其他Method重新组合。
消息转发流程如下:
-
动态方法解析(调用所属类的方法)
无法接收消息,首先会调用其所属类的这两个类方法:- 如果未实现的是实例方法,调用: +resolveInstanceMethod:
- 如果未实现的是类方法,调用:+resolveClassMethod:
备用接收者(调用所属类的方法,切换消息接受者)
如果上一个方法无法接收方法,则会切换消息接受者,即备用接受者。
- -(id)forwardingTargetForSelector:(SEL)selector
- 完整消息转发
如果上面的备用消息搞不定,就给接受者最后一次消息了。如果还是搞不定,doesNotRecognizeSelector:就会抛出异常, 此时你就会在控制台看到那熟悉的unrecognized selector sent to instance..
- -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector//返回该selector对应的方法签名
- -(void)forwardInvocation:(NSInvocation *)invocation // invocation : 封装了与那条尚未处理的消息相关的所有细节的对象
对象会创建一个表示消息的NSInvocation对象,把与尚未处理的消息有关的全部细节都封装在anInvocation中,包括selector,目标(target)和参数。我们可以在forwardInvocation方法中选择将消息转发给其它对象。
在完整消息转发能做的就是 :
在触发消息前, 先以某种方式改变消息内容, 比如追加另外一个参数, 或是改变消息等等。
实现此方法时, 如果发现某调用操作不应该由本类处理, 可以调用超类的同名方法. 则继承体系中的每个类都有机会处理该请求, 直到NSObject。
用图表示整个消息转发流程:
消息转发的实际运用:
1. 动态方法解析
前提:
1)Person中实现了readBook方法,未实现running方法。
2)在controller里面调用Person中未实现的running方法时,用readBook方法来替代running方法。
//controller里面调用person的方法
Person * person = [[Person alloc] init];
[person performSelector:@selector(running) withObject:nil afterDelay:0];
Person类:
.h:
@interface Person : NSObject
@end
.m:
#import "Person.h"
#import <objc/runtime.h>
@implementation Person
//readBook的实现
void readBook(id self,SEL _cmd){
NSLog(@"read book");
}
//当调用running方法时,系统发现未实现,则会调用这里:用readBook替换running方法
//动态方法解析
+(BOOL)resolveInstanceMethod:(SEL)sel{
NSString * selName = NSStringFromSelector(sel);
if ([selName isEqualToString:@"running"]) {
//如果转发的是running方法,则使用readBook替换running方法
class_addMethod([self class], @selector(running), (IMP)readBook, "v@:");
return YES;//YES 表示本类已经能够处理,NO表示需要消息转发机制。
}
return [super resolveInstanceMethod:sel];
}
@end
/*
扩展:
@synthesize的语义是如果你没有手动实现setter方法和getter方法,那么编译器会自动为你加上这两个方法.
@dynamic告诉编译器,属性的setter与getter方法由用户自己实现,不自动生成。
动态消息这种方案可以实现@dynamic属性的getter、setter方法。https://www.cnblogs.com/funny11/p/5585561.html
*/
2. 备用接收者
前提:
1)Person中未实现running方法。
2)Student中实现了running方法。
3)在controller里面调用Person中未实现的running方法时,用Student的running方法来替代。
//controller里面调用person的方法
Person * person = [[Person alloc] init];
[person performSelector:@selector(running) withObject:nil afterDelay:0];
Student里面实现running方法:
.h:
@interface Student : NSObject
-(void)running;
@end
.m:
@implementation Student
-(void)running{
NSLog(@"student is running");
}
@end
Person类:
.m:
@interface Person()
@property(nonatomic,strong)Student * stu;
@end
//消息转发2:备用调用者
-(id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(running)) {//Person未实现running方法,会消息转发走到这里;判断是否是running方法,是就让student去执行
if (!_stu) {
_stu = [[Student alloc] init];
}
if ([_stu respondsToSelector:@selector(running)]) {
return _stu;
}
}
return [super forwardingTargetForSelector:aSelector];
}
3. 完整消息转发
前提:
1)Person中未实现running方法。
2)Student中实现了running方法。
3)Person中未实现-(id)forwardingTargetForSelector:(SEL)aSelector{
4)在controller里面调用Person中未实现的running方法时,用Student的running方法来替代。
//controller里面调用person的方法
Person * person = [[Person alloc] init];
[person performSelector:@selector(running) withObject:nil afterDelay:0];
Student里面实现running方法:
.h:
@interface Student : NSObject
-(void)running;
@end
.m:
@implementation Student
-(void)running{
NSLog(@"student is running");
}
@end
Person类:
@interface Person()
@property(nonatomic,strong)Student * stu;
@end
//消息转发3:完整消息转发
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSString * selName = NSStringFromSelector(aSelector);
if ([selName isEqualToString:@"running"]) {
if (!_stu) {
_stu = [[Student alloc] init];
}
//student对象进行签名
return [_stu methodSignatureForSelector:aSelector];
}
return [super methodSignatureForSelector:aSelector];
}
-(void)forwardInvocation:(NSInvocation *)anInvocation{
//执行方法
SEL sel = anInvocation.selector;
if (sel == @selector(running)) {
[anInvocation invokeWithTarget:_stu];
}else{
[anInvocation invoke];
}
}
总结:
消息转发机制使得OC可以进行”多继承”,比如有一个消息中心负责处理消息,这个消息中心很多个类都要用,继承或者聚合都不是很好的解决方案,使用单例看似可以,但单例的缺点也是很明显的。这时候,把消息转发给消息中心,无疑是一个较好的解决方案。
多继承是结合不同的功能在一个对象中。它倾向于大的,多方面的对象。
另一方面,转发机制将不同的功能分配给不同的对象。它把大的问题分解成小的对象,但是通过对消息发送者透明来把这些对象关联起来。
可以顺便看看如何利用消息转发、NSProxy解决NSTimer的循环引用:消息转发、NSProxy解决NSTimer的内存泄漏