美文网首页
Runtime笔记四:动态消息转发

Runtime笔记四:动态消息转发

作者: 蔚尼 | 来源:发表于2018-05-29 17:25 被阅读21次

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

消息转发原理:

每个类都有一个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。

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

消息转发流程

消息转发的实际运用:

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

相关文章

  • 消息转发、NSProxy解决NSTimer的内存泄漏

    如果忘记消息转发,我们就先来复习一下Runtime笔记四:动态消息转发。 开始正文: 一.NSProxy是什么: ...

  • Runtime笔记四:动态消息转发

    对象在接收到未实现的消息时,会进行消息转发。 消息转发原理: 每个类都有一个methodlist,里面每个元素是指...

  • iOS - Runtime - 概念和方法交换

    runtime的概述runtime的相关概念runtime消息机制消息传递动态方法解析消息转发runtime的作用...

  • Runtime-原理

    runtime初探对象与方法的本质runtime-消息发送runtime-动态方法解析runtime-消息转发 r...

  • 详解Runtime运行时机制

    引言 简介 与Runtime交互 Runtime术语 消息 动态方法解析 消息转发 健壮的实例变量(Non Fra...

  • runtime底层实现原理

    一、Runtime介绍二、Runtime源码初探三、Runtime消息传递四、Runtime消息转发五、Runti...

  • dailyLearning -- runtime

    Runtime 简介 消息传递 消息转发 Runtime 应用 Objective-C 是一门动态语言,它会将一些...

  • iOS 底层 - 名词解析

    目录 前言 名词解析 OC消息传递和转发机制 Runtime runtime动态创建类 Runloop Metho...

  • Runtime

    定义 runtime 详解文章 1. 消息转发 动态方法解析(方法调用的必经之路) 快速转发(动态解析失败后,指...

  • iOS面试题总结2018年3月

    一、Runtime的消息转发机制1.动态方法解析 备用接受者3.完整转发 1.动态方法解析对象在接受到未知消息时,...

网友评论

      本文标题:Runtime笔记四:动态消息转发

      本文链接:https://www.haomeiwen.com/subject/fvhajftx.html