iOS消息转发

1.在说消息转发前需要先理解objc_magsent, objc_msgSend函数会依据接收者与选择子(SEL)的类型来调整适当的方法。为了完成此操作,该方法需要在接收者所属的类中搜寻其“方法列表”如果能找到与选择子名称相符的方法,就调整至其代码,如果找不到,就沿着继承体系继续向上查找,等找到合适的方法之后在跳转,如果还是找不到相符合的方法那就执行消息转发。

如图所示:


WeChat0b00accdf4f21d8395d4b563a25b77dd.png

对图的分析如下:
首先会判断对象是否为空如果为空就直接返回,不为空就继续向下执行如果是对象方法则通过isa指针找到类对象在类对象的cache(方法缓冲)中查找方法如果找到了就调用方法,如果没有找到就在类对象的方法列表中查找(class_rw_t)找到了就调用方法并把方法缓存到方法缓存cahe中,如果在方法列表中没有找到就通过reveiverClass(类对象)的superclass指针找到superclass然后同样的操作,如果没有找到就继续向上查找如果还是没有就,动态方法解析,还是没有就走消息转发:1.看看有没有其他类处理消息,没有就走方法签名,然后在走完整的消息转发还是没有就只有崩溃了三。

现在重点讲方法解析与消息转发

1.方法解析

#import 
#import 
NS_ASSUME_NONNULL_BEGIN

@interface LSPerson : NSObject
- (void)test;
@end

#import "LSPerson.h"

@implementation LSPerson
+(BOOL)resolveInstanceMethod:(SEL)sel{
    //判断是不是test方法
    if (sel == @selector(test)) {
        //获取其他方法
    Method    otherMethod = class_getInstanceMethod(self, @selector(other));
        //动态添加test
        class_addMethod(self, sel, method_getImplementation(otherMethod), method_getTypeEncoding(otherMethod));
        return YES;
    }
    NSLog(@"%s",__func__);
    return [super resolveInstanceMethod:sel];
}
- (void)other{
    NSLog(@"打印了");
}

//在其他地方调用
 LSPerson *lsP = [[LSPerson alloc]init];
        [lsP test];

通过代码可以看出来我们添加了一个other方法最后也会走other方法的实现

2.消息转发

如果我们没有对resolveInstanceMethod方法进行处理会怎么样呢?答案肯定是崩溃!!

但是我们可以在添加一个方法。

  • (id)forwardingTargetForSelector:(SEL)aSelector
#import 
#import 
NS_ASSUME_NONNULL_BEGIN

@interface LSPerson : NSObject
- (void)test;
@end

#import "LSPerson.h"
#import "LSTwo.h"
@implementation LSPerson
//- (void)test{
//    NSLog(@">>>>>");
//}
//+(BOOL)resolveInstanceMethod:(SEL)sel{
//    //判断孰不是test方法
//    if (sel == @selector(test)) {
//        //获取其他方法
//    Method    otherMethod = class_getInstanceMethod(self, @selector(other));
//        //动态添加test
//        class_addMethod(self, sel, method_getImplementation(otherMethod), method_getTypeEncoding(otherMethod));
//        return YES;
//    }
//    NSLog(@"%s",__func__);
//    return [super resolveInstanceMethod:sel];
//}
//- (void)other{
//    NSLog(@"打印了");
//}

- (id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(test)) {//判断孰不是test方法
        return [[LSTwo alloc]init];//转给其他对象处理
    }
    return [super forwardingTargetForSelector:aSelector];
}

//  Created by 谭姝姝 on 2019/8/6.
//  Copyright © 2019 罗爽. All rights reserved.
//

#import 

NS_ASSUME_NONNULL_BEGIN

@interface LSTwo : NSObject
- (void)test;
@end

#import "LSTwo.h"

@implementation LSTwo
- (void)test{
    NSLog(@"hello word");
}

//然后在调用
 LSPerson *lsP = [[LSPerson alloc]init];
        [lsP test];

//打印结果为:hello world

forwardingTargetForSelector从上面代码可以看出来是把消息转给其他对象处理

但是如果

- (id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(test:)) {
        return nil;
    }
    return [super forwardingTargetForSelector:aSelector];
}

这种会怎么样?
这时会走到完整的消息转发

3.完整消息转发

直接上码:

#import 
#import 
NS_ASSUME_NONNULL_BEGIN

@interface LSPerson : NSObject
- (void)test;
@end
  

#import "LSPerson.h"
#import "LSTwo.h"
@implementation LSPerson
//- (void)test{
//    NSLog(@">>>>>");
//}
//+(BOOL)resolveInstanceMethod:(SEL)sel{
//    //判断孰不是test方法
//    if (sel == @selector(test)) {
//        //获取其他方法
//    Method    otherMethod = class_getInstanceMethod(self, @selector(other));
//        //动态添加test
//        class_addMethod(self, sel, method_getImplementation(otherMethod), method_getTypeEncoding(otherMethod));
//        return YES;
//    }
//    NSLog(@"%s",__func__);
//    return [super resolveInstanceMethod:sel];
//}
//- (void)other{
//    NSLog(@"打印了");
//}

- (id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(test)) {//判断孰不是test方法
        return nil;
    }
    return [super forwardingTargetForSelector:aSelector];
}
//方法签名:返回值类型,参数类型 是在你没有告诉它你转发的是谁的时候才会走这个返回方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    
    if (aSelector == @selector(test)) {
        return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
    }
    return [super methodSignatureForSelector:aSelector];
}
//NSInvocation  封装一个方法调用 包括:方法 使用者,方法名,方法参数
- (void)forwardInvocation:(NSInvocation *)anInvocation{
   [anInvocation invokeWithTarget:[[LSTwo alloc]init]];
}

#import 

NS_ASSUME_NONNULL_BEGIN

@interface LSTwo : NSObject
- (void)test;
@end

#import "LSTwo.h"

@implementation LSTwo
- (void)test{
    NSLog(@"hello word");
}

一样的打印还是hello word
大家是不是有一个疑问这个和上个有什么差别??差别大了!!

NSInvocation点进去看看它的接口

import 
#include 

@class NSMethodSignature;

NS_ASSUME_NONNULL_BEGIN

NS_SWIFT_UNAVAILABLE("NSInvocation and related APIs not available")
@interface NSInvocation : NSObject {
@private
    void *_frame;
    void *_retdata;
    id _signature;
    id _container;
    uint8_t _retainedArgs;
    uint8_t _reserved[15];
}

+ (NSInvocation *)invocationWithMethodSignature:(NSMethodSignature *)sig;

@property (readonly, retain) NSMethodSignature *methodSignature;

- (void)retainArguments;
@property (readonly) BOOL argumentsRetained;

@property (nullable, assign) id target;
@property SEL selector;

- (void)getReturnValue:(void *)retLoc;
- (void)setReturnValue:(void *)retLoc;

- (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx;
- (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx;

- (void)invoke;
- (void)invokeWithTarget:(id)target;

可以获取返回值 设置返回值 获取参数 设置参数 等等。。。
其实也可以这这样

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"哈哈哈哈");
}
你还可以这样
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    int age;
    [anInvocation getArgument:&age atIndex:2];
    NSLog(@"%d",age);
}
当然上面的签名也要相应的改变 这个是处理但参数的方法的

总之在这个方法里你可以为所欲为,有兴趣的可以研究下

你可能感兴趣的:(iOS消息转发)