我们在前面文章中分别研究了方法执行过程中的快速查找流程、慢速查找流程、动态解析但是仍然有可能存在没有处理的消息,那么今天我们就来探索一下苹果给的最后一个机会消息转发流程,我自己没有处理消息,看看别人是不是可以帮我处理,开始请外援了 !!
快速转发流程
消息我处理不了了,看能不能找个外援帮忙处理一下
forwardingTargetForSelector
我们在JPerson.h中声明一个方法instanceMethod但是没有实现
@interface JPerson : NSObject
- (void)instanceMethod;
@end
在JPerson.m中实现forwardingTargetForSelector方法进行快速转发
@implementation JPerson
-(id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(instanceMethod)) {
JStudent *student = [[JStudent alloc] init];
if ([student respondsToSelector:aSelector]) {
return student;
}
}
return [super forwardingTargetForSelector:aSelector];
}
@end
在JStudent.m中实现了instanceMethod方法
@implementation JStudent
-(void)instanceMethod{
NSLog(@"student的方法");
}
@end
在main方法中
int main(int argc, const char * argv[]) {
@autoreleasepool {
JPerson *person = [JPerson alloc];
[person instanceMethod];
}
return 0;
}
我们看到消息成功转发给了JStudent的实例对象进行处理
我们看一下forwardingTargetForSelector这个方法的介绍
Returns the object to which unrecognized messages should first be directed
如果当前对象无法处理消息,那么就把消息交给另一个对象
If an object implements (or inherits) this method, and returns a non-
nil(and non-self) result, that returned object is used as the new receiver object and the message dispatch resumes to that new object. (Obviously if you returnselffrom this method, the code would just fall into an infinite loop.)如果一个对象实现了这个方法,且返回了一个非空也不是self的对象,那么这个对象就会作为消息的新的接受者进行消息处理流程
需要注意的是如果返回self那么就会造成死循环
If you implement this method in a non-root class, if your class has nothing to return for the given selector then you should return the result of invoking super’s implementation.
如果在非根类中实现了这个方法且没有什么合适的对象需要返回,那么需要执行[super forwardingTargetForSelector:]方法
This method gives an object a chance to redirect an unknown message sent to it before the much more expensive
forwardInvocation:machinery takes over. This is useful when you simply want to redirect messages to another object and can be an order of magnitude faster than regular forwarding. It is not useful where the goal of the forwarding is to capture the NSInvocation, or manipulate the arguments or return value during the forwarding.意思是说在这里进行消息转发成本还是比较低的,如果到后面慢速转发流程中再转发那么成本就高多了
总结起来这是一个负责消息转发的方法,注意不要返回self因为会造成死循环,记得执行super方法
慢速转发流程
methodSignatureForSelector & forwardInvocation
在JPerson.m中实现methodSignatureForSelector和forwardInvocation方法
@implementation JPerson
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if (aSelector == @selector(instanceMethod)) {
NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@:@"];
return signature;
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
JStudent *s = [JStudent alloc];
if ([s respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:s];
}else{
[super forwardInvocation:anInvocation];
}
}
@end
同样可以正常执行
我们可以自行看一下# methodSignatureForSelector和# forwardInvocation的介绍
Important
To respond to methods that your object does not itself recognize, you must override
methodSignatureForSelector:in addition toforwardInvocation:. The mechanism for forwarding messages uses information obtained frommethodSignatureForSelector:to create theNSInvocationobject to be forwarded. Your overriding method must provide an appropriate method signature for the given selector, either by pre formulating one or by asking another object for one.
要响应当前对象本身无法识别的方法,除了实现 forwardInvocation: 之外,您还必须重写 methodSignatureForSelector:,转发消息的机制使用从 methodSignatureForSelector: 获得的信息来创建要转发的 NSInvocation 对象。您的覆盖方法必须为给定的选择器提供适当的方法签名,或者通过预先制定一个或通过向另一个对象询问一个。
这个方法很少用,通过前面快速转发的介绍可以知道使用慢速转发的成本要高很多,这是苹果给的最后一次机会,再不处理可就要报错了
汇编验证
我们通过bt名称查看堆栈信息的时候可以看到
frame #5: 0x00007fff20404fd6 CoreFoundation`___forwarding___ + 819
frame #6: 0x00007fff20404c18 CoreFoundation`_CF_forwarding_prep_0 + 120
......
因为消息转发流程是在尚未开源的CoreFoundation库中,我们通过Hopper反汇编窥探一下其流程,将CoreFoundation拖入到Hopper中搜索_forwarding_prep_0
双击进入____forwarding___
这里执行了
forwardingTargetForSelector方法进行快速转发流程,如果快速转发流程返回nil就跳转到loc_64a67
这里判断是否实现了methodSignatureForSelector方法,如果没有实现就跳转到loc_64dd7
最终执行了doesNotRecognizeSelector进行报错,如果实现了methodSignatureForSelector方法就继续执行_forwardStackInvocation -> forwardInvocation
为什么resolveInstanceMethod方法被执行了两次
在上文中我们发现resolveInstanceMethod被执行了两次这是为什么呢??
第一次是在查询不到方法的时候调用了一次。
第二次是在进行完消息转发流程之后调用了class_getInstanceMethod
class_getInstanceMethod
/***********************************************************************
* class_getInstanceMethod. Return the instance method for the
* specified class and selector.
**********************************************************************/
Method class_getInstanceMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
// This deliberately avoids +initialize because it historically did so.
// This implementation is a bit weird because it's the only place that
// wants a Method instead of an IMP.
#warning fixme build and search caches
// Search method lists, try method resolver, etc.
lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);
#warning fixme build and search caches
return _class_getMethod(cls, sel);
}
这里执行了lookUpImpOrForward并且behavior参数传入了LOOKUP_RESOLVER
NEVER_INLINE
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
......
// 前面快速慢速查找都找不到,所以这里还可以再执行一次动态解析的流程
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
......
}
在转发流程中可能通过runtime方法添加了相应方法,所以在执行完转发流程之后如果没有处理消息苹果又再一次去走了一遍查询流程。
小结
如果经过经过查找和动态解析之后仍然没有处理消息那么苹果还给了最后一次机会进行消息转发,如果最后这次机会也没把握住那就该报错了。