前言
当我们在class
的cache
和methodlist
中都查找不到对应的方法时,会执行消息处理流程。消息处理流程主要包括动态方法决议、快速消息转发、慢速消息转发;我们已经了解了动态方法决议,本文将分析下快速消息转发、慢速消息转发相关的内容;
通过objc
方法log
查看信息
- 上一篇文章我们已经知道了
oc
底层调用方法时可以控制是否记录日志,通过以下代码我们开启了日志
// 慢速查找
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
GCPerson *person = [GCPerson alloc];
[GCPerson say];
NSLog(@"Hello, World!");
}
return 0;
}
复制代码
- 由于我们并未在
GCPerson
中实现方法say:
所以系统会因为方法找不到而抛出异常;
3. 但是在抛出异常前也是会记录调用日志的,我们已经知道路径日志的是/tmp/msgSends
,通过cmd+shift+g
到对应目录下查看日志
4. 最终发现在抛出异常前,调用了+ GCPerson NSObject forwardingTargetForSelector:
和+ GCPerson NSObject methodSignatureForSelector:
消息转发
快速转发forwardingTargetForSelector
- 通过查阅官方文档我们大概知道了
forwardingTargetForSelector
方法的作用,在当前类无法找到方法实现时,可以将方法转交给其他对象来进行处理;
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 return self from this method, the code would just fall into an infinite loop.)
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.
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.
翻译
如果一个对象实现(或继承)此方法,并返回一个非nil(和非self)结果,则该返回的对象将用作新的接收方对象,消息分派将继续到该新对象。(显然,如果从这个方法返回self,代码将陷入无限循环。)
如果你在非根类中实现这个方法,如果你的类对于给定的选择器没有返回任何东西,那么你应该返回调用super的实现的结果。
该方法使对象有机会在昂贵得多的forwardInvocation:机制接管之前,重定向发送给它的未知消息。当您只想将消息重定向到另一个对象,并且比常规转发快一个数量级时,这很有用。如果转发的目标是捕获NSInvocation,或者在转发期间操作参数或返回值,那么它就没有用了。
复制代码
forwardingTargetForSelector
支持类方法和对象方法两种形式
- 将对象方法重定向到某个
实例对象
时,需要实现-forwardingTargetForSelector
并返回对应的实例对象; - 将类方法重定向到某个类对象时,需要实现
+forwardingTargetForSelector
并返回对应的类对象;
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%s-%@",__func__, NSStringFromSelector(aSelector));
if (aSelector == @selector(sayHello)) {
return [GCStudent alloc];//返回可以处理当前方法的实例对象
}
return [super forwardingTargetForSelector:aSelector];
}
+ (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%s-%@",__func__, NSStringFromSelector(aSelector));
if (aSelector == @selector(say666)) {
return [GCStudent class];//返回可以处理当前方法的类对象
}
return [super forwardingTargetForSelector:aSelector];
}
复制代码
- 快速转发的应用场景一般比较少,因为如果当前类找不到方法,就通过
快速转发
来转给其他类,这种场景下其他类也不太可能会实现当前缺失的方法,所以,被转发的类相当于是背锅侠;
慢速转发 methodSignatureForSelector
&& forwardInvocation
- 当快速转发找不到合适的类进行转发,即没有合适的背锅侠时候我们会进入消息慢速转发
methodSignatureForSelector
和forwardInvocation
;
methodSignatureForSelector
和forwardInvocation
都有对象方法和类方法两种形式,且methodSignatureForSelector
和forwardInvocation
必须成对出现;methodSignatureForSelector
里面需要返回当前方法签名,由于对签名的要求并不严格,只要返回可识别的签名都可以往下执行,所以这里的签名可以不加参数长度的信息;注意只有返回了签名信息forwardInvocation
才会执行,- 使用慢速转发的具体代码如下
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(sayHello)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"forwardInvocation");
}
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(say666)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"forwardInvocation");
}
复制代码
- 慢速消息转发触发时,我们可以在
+ (void)forwardInvocation:
做更多的事,进行更灵活的处理,让转发的消息尽可能的沿着正常的逻辑执行;
- (void)forwardInvocation:(NSInvocation *)anInvocation {
//根据实际需要进行灵活处理;
GCStudent *student = [GCStudent alloc];
if ([self respondsToSelector:anInvocation.selector]) {//如果当前类可以处理交给当前类
[anInvocation invoke];
}else if ([student respondsToSelector:anInvocation.selector]){
[anInvocation invokeWithTarget:student];//如果student可以处理方法,交给student进行处理;
}
NSLog(@"forwardInvocation");
}
复制代码
消息转发使用场景
- 不管是快速消息转发还是慢速消息转发,都只能作为一种异常处理的手段,而不能作为最终解决办法;当代码执行到这里的时候即便通过消息转发可以让应用不崩溃,但是应用本身的业务将会处于混乱不可控的状态;
- 消息转发由于机制的特殊性需要不断的向父类查找,对性能也是一种浪费;
- 建议将消息机制作为一种容错手段但不要过度依赖;
doesNotRecognizeSelector
doesNotRecognizeSelector
作为最后抛出异常的一个方法,重写它可以在抛出异常前收到相关信息;doesNotRecognizeSelector
也有对应的+方法
和-方法
两种形式;doesNotRecognizeSelector
但并不能够真正对异常做任何有效的处理,重写这个方法对实际的容错机制意义不大;
消息转发三重防护流程图
动态决议、消息快速转发、消息慢速转发最终的流程图如下
消息机制的探索过程分析理解
- 当方法无法找到发生崩溃时,通过查看堆栈信息,发现问题出在
CoreFoundation___forwarding___
和CoreFoundation __forwarding_prep_0___
方法中
- 通过使用hopper反汇编
CoreFoundation
库,搜索___forwarding_prep_0___
查看对应信息;这里不再过多说明,有兴趣的可以从Xcode内置的corefoundation中查看对应的反汇编信息;
3.
- 僵尸对象:内存已经被回收的对象;
- 野指针:指向僵尸对象的指针;
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END