iOS Runtime 消息转发机制原理
最近看到很多面试问道 runtime是个啥,咋实现的、具体实现的步骤,针对此类问题我做了一下整理,可能帮到小伙伴的可以看看。废话不多说直接走起
我们在开发过程中,经常会遇到这样的报错,当我们调用一个对象不存在的方法的时候
系统报错 提示如下错误
"-[YBHomeHeaderCRView pushMessage:]: unrecognized selector sent to instance 0x7fc77cf0de40"
其实这种报错都是iOS消息转发机制在无法响应的情况后抛出的问题。那我们来深入的看一下这个消息机制是这么走的。
这么进入的消息机制的
1.首先通过[YBHomeHeaderCRView new]对象的ISA指针找打它对应的class。
2.首先在class的cache用查找是否有sendMessage方法,没有再去class的method list 查找,找到并将其加入到cache中,方便下次调用。
3.如果没有就去它的superclass里继续查找。也是先cache,在methodlists。
4.一旦查找到对应的函数方法,通过函数指针IMP调转到对应的函数中去执行。
5.如果一直没有找到,就执行消息转发。
消息机制原理
我们先看一下下面这个结构图,先对整个消息处理机制有一个初步的认识
从结构图来看,消息转发机制共分为3大步骤:
1.Method resolution 动态方法解析处理阶段
2.Fast forwarding 快速转发阶段
3.Normal forwarding 慢速转发阶段
如果想要不抛出unrecognized selector 的报错,也就需要从这3步里面来做补救。
第一步 动态方法解析处理阶段
如果调用了对象方法首先会进行+(BOOL)resolveInstanceMethod:(SEL)sel判断
如果调用了类方法 首先会进行 +(BOOL)resolveClassMethod:(SEL)sel判断
如果YES则能接受消息,NO则不能接受消息 则就回进入第二步
我们先调一下对象方法,然后在resolveInstanceMethod进行补救,
void pushMessage(id self, SEL _cmd, NSString *msg){
NSLog(@"------%@",msg);
}
+(BOOL)resolveInstanceMethod:(SEL)sel{
NSString *methodName =NSStringFromSelector(sel);
if ([methodName isEqualToString:@"pushMessage:"]) {
return class_addMethod(self, sel, (IMP)pushMessage, "v@:@");
}
return NO;
}
经过上面类型的补救,果然对象方法不在抛出异常了,并且打印了数据
第二歩:快速转发阶段
如果在上一步的方法内返回的为YES则能接受消息 NO不能接受消息 则进入第二步,我们先把上面方法内的处理方案注释掉,让消息转发进入第二步。
我们新创建一个YBMessage类,里面声明和实现pushMessage方法,用来当作备用响应者。
-(id)forwardingTargetForSelector:(SEL)aSelector{
NSString *methodName =NSStringFromSelector(aSelector);
if ([methodName isEqualToString:@"pushMessage:"]) {
return [YBMessage new];
}
return [super forwardingTargetForSelector:aSelector];
}
因为一个对象内部可能还有其他可能响应的对象,所以这个方法是转发SEL去对象内部的其他可以响应该方法的对象。
第三歩 慢速转发阶段
如果第2步返回self或者nil,则说明没有可以响应的目标 则进入第三步。第三步的消息转发机制本质上跟第二步是一样的都是切换接受消息的对象,但是第三步切换响应目标更复杂一些,需要手动将响应方法切换给备用响应对象。
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSString*methodName =NSStringFromSelector(aSelector);
if([methodNameisEqualToString:@"pushMessage:"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
-(void)forwardInvocation:(NSInvocation *)anInvocation{
SELsel = [anInvocationselector];
YBMessage *msg = [YBMessage new];
if([msg isEqualToString:sel]) {
[anInvocationinvokeWithTarget:msg];
}else{
[superforwardInvocation:anInvocation];
}
}
//例子"v@:@"
//v@:@ v:返回值类型void;@ id类型,执行sel的对象;:SEL;@参数
如果前三个都没有找到方法
- (void)doesNotRecognizeSelector:(SEL)aSelector{
NSLog(@"找不到方法");
}
同时,越往后面处理代价越高,最好的情况是在第一步就处理消息,这样runtime会在处理完后缓存结果,可以提高处理效率。
总结:
在一个函数找不到时,OC提供了三种方式去补救:
1、调用resolveInstanceMethod给个机会让类添加这个实现这个函数
2、调用forwardingTargetForSelector让别的对象去执行这个函数
3、调用forwardInvocation(函数执行器)灵活的将目标函数以其他形式执行。
如果都不中,调用doesNotRecognizeSelector抛出异常。