消息机制

消息机制流程

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


    消息机制_第1张图片
    image.png

动态方法解析

  • 如果在当前类,父类都没有找到该方法,就会进入动态方法解析阶段.
  • 进入动态方法解析前,会做判断,如果没有解析过才进入,如果解析过就不会再次进入方法解析阶段.会直接进入消息转发阶段.
  • 可以实现以下方法,来动态添加方法实现
    +resolveInstanceMethod:
    +resolveClassMethod:
  • 动态解析过后,会重新走“消息发送”的流程,“从receiverClass的cache中查找方法”这一步开始执行.
消息机制_第2张图片
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 
#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个版本(前面可以是加号+,也可以是减号-)


    消息机制_第3张图片
    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的指令,可以将具体的类型表示成字符串编码


消息机制_第4张图片

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 
#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方法找不到

你可能感兴趣的:(消息机制)