iOS Runtime 消息转发机制
运行时阶段的消息发送的详细步骤如下:
1,检测selector是不是需要忽略的。比如 Mac OS X 开发,有了垃圾回收就不会理会retain、release 这些函数了。
2,检测target 是不是nil对象。ObjC 的特性是允许对一个 nil对象执行任何一个方法不会Crash,因为会被忽略掉。
3,如果上面两个都通过了,那就开始查找这个类的 IMP ,先从 cache 里面找,若可以找得到就跳到对应的函数去执行。
4,若果在cache里找不到就找一下方法列表methodLists。
5,如果methodLists找不到,就到超类的方法列表里查找,一直找,直到找打NSObject类为止。
6,如果还找不到,Runtime就提供了如下三种方法来处理:动态方法解析、消息接收者重定向、消息重定向,这三种方法调用关系如下图:
消息转发机制.jpeg消息机制共分为3大步骤:
1.Method resolution 方法解析处理阶段
+(BOOL)resolveInstanceMethod:(SEL)sel;
+(BOOL)resolveClassMethod:(SEL)sel;
2.Fast forwarding 快速转发阶段
-(id)forwardingTargetForSelector:(SEL)aSelector;
3.Normal forwarding 常规转发阶段
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
-(void)forwardInvocation:(NSInvocation *)anInvocation;
那么如果想要不抛出unrecognized selector 的报错,也就需要从这3步里面来做补救。
第一步:Method resolution 方法解析处理阶段
如果调用了对象方法首先会进行+(BOOL)resolveInstanceMethod:(SEL)sel判断
如果调用了类方法 首先会进行 +(BOOL)resolveClassMethod:(SEL)sel判断
+(BOOL)resolveInstanceMethod:(SEL)sel {
//判断是否为外部调用的方法
if ([NSStringFromSelector(sel) isEqualToString:@"testFunction"]) {
/**
对类进行对象方法 需要把方法添加进入类内
*/
[LMRuntimeTool addMethodWithClass:[self class] withMethodSel:sel withImpMethodSel:@selector(addDynamicMethod)];
return YES;
}
return [super resolveInstanceMethod:sel];
}
两个方法都为类方法,如果YES则能接受消息 NO不能接受消息 进入第二步
第二步:Fast forwarding 快速转发阶段 (后面阶段都针对对象来处理,不考虑类方法)
-(id)forwardingTargetForSelector:(SEL)aSelector{
if ([NSStringFromSelector(aSelector) isEqualToString:@"testFunction"]) {
return [BackupTestMessage new];
}
return [super forwardingTargetForSelector:aSelector];
}
第三步:Normal forwarding 常规转发阶段
如果第2步返回self或者nil,则说明没有可以响应的目标 则进入第三步。
第三步的消息转发机制本质上跟第二步是一样的都是切换接受消息的对象,但是第三步切换响应目标更复杂一些,第二步里面只需返回一个可以响应的对象就可以了,第三步还需要手动将响应方法切换给备用响应对象。
第三步有2个步骤:
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
//如果返回为nil则进行手动创建签名
if ([super methodSignatureForSelector:aSelector]==nil) {
NSMethodSignature * sign = [NSMethodSignature signatureWithObjCTypes:"v@:"];
return sign;
}
return [super methodSignatureForSelector:aSelector];
}
-(void)forwardInvocation:(NSInvocation *)anInvocation {
//创建备用对象
BackupTestMessage * backUp = [BackupTestMessage new];
SEL sel = anInvocation.selector;
//判断备用对象是否可以响应传递进来等待响应的SEL
if ([backUp respondsToSelector:sel]) {
[anInvocation invokeWithTarget:backUp];
}else{
// 如果备用对象不能响应 则抛出异常
[self doesNotRecognizeSelector:sel];
}
}
总结:
1,从以上的代码可以看出,forwardingTargetForSelector仅支持一个对象的返回,也就是说消息只能被转发给一个对象,而forwardInvocation可以将消息同事转发给任意多个对象,这就是两者的最大区别。
2,虽然理论上可以重载doesNotRecognizeSelector函数实现保证不抛出异常(不调用super实现),但是苹果文档着重提出"一定不能让这个函数就这么结束掉,必须抛出异常"。
3,forwardInvocation甚至能够修改消息的内容,用于实现更强大的功能。
参考链接:
https://www.jianshu.com/p/fdd8f5225f0c
https://www.jianshu.com/p/d4b55dae9a0d