消息机制流程
- 消息发送
- 动态方法解析
-
消息转发
image.png
动态方法解析
- 如果在当前类,父类都没有找到该方法,就会进入
动态方法解析
阶段. - 进入
动态方法
解析前,会做判断,如果没有解析过才进入,如果解析过就不会再次进入方法解析阶段.会直接进入消息转发
阶段. - 可以实现以下方法,来动态添加方法实现
+resolveInstanceMethod:
+resolveClassMethod:
- 动态解析过后,会重新走“消息发送”的流程,“从receiverClass的cache中查找方法”这一步开始执行.
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]
-
动态解析
阶段处理后 会重新走消息发送
阶段,通过方法缓存列表 isa
和superclass
去查找该方法. -
消息转发
阶段 不会重新走消息发送
.因为消息转发
阶段是在消息发送
和动态方法
解析两个阶段之后的.这两个阶段都没处理该方法.才会进入消息转发
阶段.在这个阶段即便处理了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联合使用的情况
- 注意在
forwardingTargetForSelector
的class
和instance
方法中返回的对象类型.
+ (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]
会调用LDCat
的instance
方法
如果返回[LDCat class]
会调用LDCat
的class
方法
- (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
成员变量.并且自动生成_age
的setter
和getter
的方法声明.
@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方法找不到
网友评论