消息机制

作者: 曹来东 | 来源:发表于2018-09-04 16:58 被阅读39次

    消息机制流程

    • 消息发送
    • 动态方法解析
    • 消息转发


      image.png

    动态方法解析

    • 如果在当前类,父类都没有找到该方法,就会进入动态方法解析阶段.
    • 进入动态方法解析前,会做判断,如果没有解析过才进入,如果解析过就不会再次进入方法解析阶段.会直接进入消息转发阶段.
    • 可以实现以下方法,来动态添加方法实现
      +resolveInstanceMethod:
      +resolveClassMethod:
    • 动态解析过后,会重新走“消息发送”的流程,“从receiverClass的cache中查找方法”这一步开始执行.
    image.png

    instance对象方法动态解析resolveInstanceMethod

    //- (void)test{
    //    NSLog(@"%s",__func__);
    //}
    - (void)other{
        NSLog(@"%s",__func__);
    }
    
    + (BOOL)resolveInstanceMethod:(SEL)sel{
        //声明了test方法,但是没有实现
        //这里判断是不是处理的目标方法
        //实现了这个方法,所有找不到的方法都会进入,
        //我们只处理目标方法test
        if (sel == @selector(test)) {
            //动态添加test方法实现.test为对象方法,对象方法在class对象当中,因为是+类方法,所以self
            //即为class对象,不要传入meta-class对象.
            //sel:给哪个SEL动态添加方法
            
            //获取其他方法
            Method otherMethod = class_getInstanceMethod(self, @selector(other));
            
            //method_getImplementation(otherMethod)  方法实现
            //method_getTypeEncoding(otherMethod)    方法Type Encoding
            
            class_addMethod(self, sel, method_getImplementation(otherMethod), method_getTypeEncoding(otherMethod));
            return YES;
            
        }
        return [super resolveInstanceMethod:sel];
    }
    

    method_t 和 Method是一种数据结构

    struct method_t{
        SEL sel;
        char *types;
        IMP imp;
    };
    - (void)other{
        NSLog(@"%s",__func__);
    }
    //获取self对象的other方法.
     Method otherMethod = class_getInstanceMethod(self, @selector(other));
    //将 otherMethod 转成struct method_t *的otherMethod_t
            struct method_t *otherMethod_t = otherMethod;
    //通过打印可以看到两个数据结构式相同的
      NSLog(@"%s %s %p",otherMethod_t->sel,otherMethod_t->types,otherMethod_t->imp);
    //打印信息
    2018-09-04 15:44:45.754911+0800 Block[21609:4298064] other v16@0:8 0x1066674b0
    (lldb) p (IMP)0x1066674b0
    (IMP) $0 = 0x00000001066674b0 (Block`-[LDPerson other] at LDPerson.m:19)
    (lldb) 
    //如下代码的效果是:为test方法动态添加一个方法,这个方法就是other方法
    //后两个参数是other的实现imp,other方法的types.调用test方法时就会调用到动态添加的other方法.
    class_addMethod(self, sel, otherMethod, otherMethod_t->imp,otherMethod_t->types)
    
    

    class对象方法动态解析resolveClassMethod

        LDPerson * person  = [[LDPerson alloc] init];
        [person test];//instance方法
        [LDPerson test];//class方法
    @interface LDPerson : NSObject
    + (void)test;
    - (void)test;
    #import <objc/runtime.h>
    #import "LDPerson.h"
    
    @implementation LDPerson
    //- (void)test{
    //    NSLog(@"%s",__func__);
    //}
    + (void)other{
        NSLog(@"calss %s",__func__);
    }
    - (void)other{
        NSLog(@"instance %s",__func__);
    }
    //class 方法解析
    + (BOOL)resolveClassMethod:(SEL)sel{
        
        if (sel == @selector(test)) {
            
            Method otherMethod = class_getInstanceMethod(self, @selector(other));
            //添加到meta-class对象中.注意区分objc_getClass(self)
            class_addMethod(object_getClass(self), sel, method_getImplementation(otherMethod), method_getTypeEncoding(otherMethod));
            return YES;
            
        }
        return [super resolveInstanceMethod:sel];
    }
    //instance 方法解析
    - (BOOL)resolveInstanceMethod:(SEL)sel{
        
        if (sel == @selector(test)) {
          
            Method otherMethod = class_getInstanceMethod(self, @selector(other));
    
            class_addMethod(self, sel, method_getImplementation(otherMethod), method_getTypeEncoding(otherMethod));
            return YES;
            
        }
        return [super resolveInstanceMethod:sel];
    }
    //打印结果会都调用instance的other方法
    2018-09-04 16:11:30.245693+0800 Block[22205:4457289] instance -[LDPerson other]
    2018-09-04 16:11:30.245817+0800 Block[22205:4457289] instance -[LDPerson other]
    @end
    

    消息转发

    • 可以在forwardInvocation:方法中自定义任何逻辑
    • 以上方法都有对象方法、类方法2个版本(前面可以是加号+,也可以是减号-)


      image.png

    消息转发第一阶段:forwardingTargetForSelector注意点:

    - (void)viewDidLoad {
        [super viewDidLoad];
        LDPerson * person  = [[LDPerson alloc] init];
        [person test];//instance方法
        [person test];//instance方法
    }
    @implementation LDCat
    
    - (void)test{
        
        NSLog(@"---%s",__func__);
    }
    
    @end
    - (id)forwardingTargetForSelector:(SEL)aSelector{
        
        if (aSelector == @selector(test)) {
            NSLog(@"111");
            return [[LDCat alloc] init];
        }
        return [super forwardingTargetForSelector:aSelector];
    }
    //打印结果
    2018-09-05 07:31:33.651163+0800 Block[26261:5077882] 111
    2018-09-05 07:31:33.651421+0800 Block[26261:5077882] ----[LDCat test]
    2018-09-05 07:31:37.307880+0800 Block[26261:5077882] 111
    2018-09-05 07:31:37.308108+0800 Block[26261:5077882] ----[LDCat test]
    
    • 动态解析阶段处理后 会重新走 消息发送阶段,通过方法缓存列表 isasuperclass去查找该方法.
    • 消息转发阶段 不会重新走 消息发送.因为消息转发阶段是在消息发送动态方法解析两个阶段之后的.这两个阶段都没处理该方法.才会进入消息转发阶段.在这个阶段即便处理了LDPerson对象的方法.即:在LDPerson类的-forwardingTargetForSelector返回LDCat对象. LDPerson方法缓存列表也不会有LDCat对象的test方法.当第二次调用MJPerson instance对象的test方法时,还是会经过 消息发送,动态方法解析 然后再次来到LDPerson类的-forwardingTargetForSelector方法.但是因为这个方法返回的是LDCat对象,所以在LDCat对象的方法缓存列表中会有test方法.
    • 只有在消息发送阶段才会做方法缓存,动态方法解析 和 消息转发阶段 都不会做当前类方法缓存,如果在当前类返回其他类对象,方法缓存是在其他类中做的,与当前类无关.
    • 如果在forwardingTargetForSelector中返回nil,就会执行(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector这个方法.

    消息转发第二阶段: methodSignatureForSelector方法

    • 下面代码是在 消息转发 的第二个阶段methodSignatureForSelector
    - (id)forwardingTargetForSelector:(SEL)aSelector{
        
        if (aSelector == @selector(test)) {
            return nil;
        }
        return [super forwardingTargetForSelector:aSelector];
    }
    //方法签名包含:返回值类型,参数类型及个数
    //如果这个方法返回了一个有效的方法签名,就会调用forwardInvocation方法
    //在forwardInvocation方法中可以任意处理这个方法,而不会报错
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
        if (aSelector == @selector(test)) {
            //注意返回的是一个C语言字符串,不是@""
            //v 函数返回值void 16表示参数所占的字节数.@表示第一个参数为id类型
            //0:表示第一个参数从第零个字节开始
            //8表示第二个参数是从第八个字节开始的
            //: 表示@selector具体Type Encoding编码在最后面
            return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
        }
        return [super methodSignatureForSelector:aSelector];
    }
    - (void)forwardInvocation:(NSInvocation *)anInvocation{
        NSLog(@"forwardInvocation");
    }
    
    • 如果在methodSignatureForSelector方法中返回nil,就会直接报方法找不到的错误.不会进入forwardInvocation方法.
    • 在这个方法中也可以做如下返回
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
        if (aSelector == @selector(test)) {
            //因为我们在LDCat中实现了test方法,所以可以这样返回
            //return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
            return [[[LDCat alloc] init] methodSignatureForSelector:aSelector];
            
        }
        return [super methodSignatureForSelector:aSelector];
    }
    

    forwardingTargetForSelector, methodSignatureForSelector, forwardInvocation联合使用的情况

    • 注意在forwardingTargetForSelectorclassinstance方法中返回的对象类型.
    + (id)forwardingTargetForSelector:(SEL)aSelector{
        
        if (aSelector == @selector(test)) {
            return nil;
        }
        return [super forwardingTargetForSelector:aSelector];
    }
    - (id)forwardingTargetForSelector:(SEL)aSelector{
        
        if (aSelector == @selector(test)) {
            return nil;
        }
        return [super forwardingTargetForSelector:aSelector];
    }
    
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
        if (aSelector == @selector(test)) {
           
            return [[[LDCat alloc] init] methodSignatureForSelector:aSelector];
        }
        return [super methodSignatureForSelector:aSelector];
    }
    + (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
        if (aSelector == @selector(test)) {
            
           return [[LDCat class] methodSignatureForSelector:aSelector];
    ;
        }
        return [super methodSignatureForSelector:aSelector];
    }
    - (void)forwardInvocation:(NSInvocation *)anInvocation{
        NSLog(@"- - forwardInvocation");
    }
    + (void)forwardInvocation:(NSInvocation *)anInvocation{
        NSLog(@"++++forwardInvocation");
    }
    

    这样写并没有调用到LDCat方法

    • 只是在methodSignatureForSelector方法中返回了LDCat对象,是否真正调用的逻辑在forwardInvocation方法中
    - (int )test:(int)number{
        NSLog(@"LDCat == test");
        return 2 * number;
    }
    
    - (id)forwardingTargetForSelector:(SEL)aSelector{
        
        if (aSelector == @selector(test:)) {
            
            return nil;
        }
        return [super forwardingTargetForSelector:aSelector];
    }
    
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
        
        
        if (aSelector == @selector(test:)) {
           
            return [[[LDCat alloc] init] methodSignatureForSelector:aSelector];
        }
        return [super methodSignatureForSelector:aSelector];
    }
    
    - (void)forwardInvocation:(NSInvocation *)anInvocation{
        //参数顺序: receiver, selector, other arguments
        int ret;
        [anInvocation getArgument:&ret atIndex:2];
        NSLog(@"%d",ret);
    }
    

    这样写才会调用LDCat的方法

    //外部调用
         LDPerson * person  = [[LDPerson alloc] init];
         [person test:10];//instance方法
    //LDCat的实现
    - (int )test:(int)number{
        NSLog(@"LDCat == test");
        return 2 * number;
    }
    //LDPerson.m实现
    - (id)forwardingTargetForSelector:(SEL)aSelector{
        
        if (aSelector == @selector(test:)) {
            
            return nil;
        }
        return [super forwardingTargetForSelector:aSelector];
    }
    
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
        
        
        if (aSelector == @selector(test:)) {
           
            return [[[LDCat alloc] init] methodSignatureForSelector:aSelector];
        }
        return [super methodSignatureForSelector:aSelector];
    }
    
    - (void)forwardInvocation:(NSInvocation *)anInvocation{
         //调用者anInvocation.target 开始是LDPerson,在此方法中处理后变成LDCat
        //方法名anInvocation.selector: test:
        //方法参数: 10
        [anInvocation invokeWithTarget:[[LDCat alloc] init]];
        int ret ;
        [anInvocation getReturnValue:&ret];
        NSLog(@"%d",ret);
    }
    //打印结果
    2018-09-05 10:12:32.193301+0800 Block[29949:5898058] LDCat == test
    2018-09-05 10:12:32.193441+0800 Block[29949:5898058] 20
    
    

    注意: methodSignatureForSelector返回的方法签名问题

    • 在方法methodSignatureForSelector中反回了LDCat的方法签名( return [[[LDCat alloc] init] methodSignatureForSelector:aSelector]; ),因为LDCat和LDPerson中都有同名的方法,所以方法签名是相同的.所以可以这样写.
    • 然后在forwardInvocation方法中重新将这个方法交给了另一个instance对象处理
    • return [[[LDCat alloc] init] methodSignatureForSelector:aSelector];
    • [anInvocation invokeWithTarget:[[LDCat alloc] init]];
    • 上面两句代码是两个instance对象,第一个用来获取方法签名,第二个用来处理方法.

    forwardInvocation方法

    • 这个方法中的NSInvocation对象可以获取返回值
    • NSInvocation对象封装了一个方法调用,包括:方法调用者,方法名,方法参数
    - (void)forwardInvocation:(NSInvocation *)anInvocation{
        //将这个方法交给LDCat去解决
        [anInvocation invokeWithTarget:[[LDCat alloc] init]];
        //如果该方法有返回值,可通过getReturnValue方法获取返回值
        //注意传进去的参数是地址:&ret
        int ret;
        [anInvocation getReturnValue:&ret];
        NSLog(@"%d",ret);
    }
    

    forwardingTargetForSelector注意点

    • 即便forwardingTargetForSelector是"-"的 instance方法,但是在返回的时候:
      如果返回[[LDCat alloc] init]会调用LDCatinstance方法
      如果返回[LDCat class]会调用LDCatclass方法
    - (id)forwardingTargetForSelector:(SEL)aSelector{
        
        if (aSelector == @selector(test:)) {
            return [[LDCat alloc] init];
            return [LDCat class];
        }
        return [super forwardingTargetForSelector:aSelector];
    }
    

    @synthesize @dynamic

    @property (nonatomic,assign) int age;
    @synthesize age = _age;
    
    • 表示会为age这个属性生成_age成员变量.并且自动生成_agesettergetter的方法声明.

    @dynamic age表示:

    不要编译器生成setter 和getter的实现,不要自动生成_成员变量

    Type Encoding

    -iOS中提供了一个叫做@encode的指令,可以将具体的类型表示成字符串编码


    runtime消息转发阶段的用途

    • 可以为某个类添加消息转发代码来处理 方法找不到的问题,可以保证程序不会Crash
    • 实现了test方法, other和runtimeMethod方法没有实现.
    • 可以看到打印,而不会Crash
    • 也可为NSObject添加分类,保证低Crash
    - (void)viewDidLoad {
        [super viewDidLoad];
        LDPerson * person = [[LDPerson alloc] init];
        [person test];
        [person other];
        [person runtimeMethod];
    }
    @interface LDPerson : NSObject
    - (void)test;
    - (void)runtimeMethod;
    
    - (void)other;
    @end
    #import <objc/runtime.h>
    #import "LDPerson.h"
    
    
    @implementation LDPerson
    - (void)test{
        NSLog(@"%s",__func__);
    }
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
        //本来能调用的方法
        if ([self respondsToSelector:aSelector]) {
          return [super methodSignatureForSelector:aSelector];
        }
        
        //不能调用的方法
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    - (void)forwardInvocation:(NSInvocation *)anInvocation{
        
        NSLog(@"%@类的%@方法找不到",[self class],NSStringFromSelector(anInvocation.selector));
    }
    @end
    //打印结果
    2018-09-05 14:48:30.642499+0800 Block[35868:7127114] -[LDPerson test]
    2018-09-05 14:48:32.902632+0800 Block[35868:7127114] LDPerson类的other方法找不到
    2018-09-05 14:48:32.902929+0800 Block[35868:7127114] LDPerson类的runtimeMethod方法找不到
    

    相关文章

      网友评论

        本文标题:消息机制

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