1.在说消息转发前需要先理解objc_magsent, objc_msgSend函数会依据接收者与选择子(SEL)的类型来调整适当的方法。为了完成此操作,该方法需要在接收者所属的类中搜寻其“方法列表”如果能找到与选择子名称相符的方法,就调整至其代码,如果找不到,就沿着继承体系继续向上查找,等找到合适的方法之后在跳转,如果还是找不到相符合的方法那就执行消息转发。
如图所示:
对图的分析如下:
首先会判断对象是否为空如果为空就直接返回,不为空就继续向下执行如果是对象方法则通过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);
}
当然上面的签名也要相应的改变 这个是处理但参数的方法的
总之在这个方法里你可以为所欲为,有兴趣的可以研究下