Runtime笔记四:动态消息转发

对象在接收到未实现的消息时,会进行消息转发。

消息转发原理:

每个类都有一个methodlist,里面每个元素是指向method结构体的指针。每个Method结构体里面包含一个SEL和一个对应的IMP,消息转发就是将原本的SEL和IMP的这种对应关系分开,和其他Method重新组合。

消息转发流程如下:

  1. 动态方法解析(调用所属类的方法)
    无法接收消息,首先会调用其所属类的这两个类方法:

    • 如果未实现的是实例方法,调用: +resolveInstanceMethod:
    • 如果未实现的是类方法,调用:+resolveClassMethod:
  2. 备用接收者(调用所属类的方法,切换消息接受者)
    如果上一个方法无法接收方法,则会切换消息接受者,即备用接受者。

  • -(id)forwardingTargetForSelector:(SEL)selector
  1. 完整消息转发
    如果上面的备用消息搞不定,就给接受者最后一次消息了。如果还是搞不定,doesNotRecognizeSelector:就会抛出异常, 此时你就会在控制台看到那熟悉的unrecognized selector sent to instance..
  • -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector//返回该selector对应的方法签名
  • -(void)forwardInvocation:(NSInvocation *)invocation // invocation : 封装了与那条尚未处理的消息相关的所有细节的对象

对象会创建一个表示消息的NSInvocation对象,把与尚未处理的消息有关的全部细节都封装在anInvocation中,包括selector,目标(target)和参数。我们可以在forwardInvocation方法中选择将消息转发给其它对象。

在完整消息转发能做的就是 :
在触发消息前, 先以某种方式改变消息内容, 比如追加另外一个参数, 或是改变消息等等。
实现此方法时, 如果发现某调用操作不应该由本类处理, 可以调用超类的同名方法. 则继承体系中的每个类都有机会处理该请求, 直到NSObject。

用图表示整个消息转发流程:

Runtime笔记四:动态消息转发_第1张图片
消息转发流程

消息转发的实际运用:

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 

@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的内存泄漏

你可能感兴趣的:(Runtime笔记四:动态消息转发)