iOS八股文(七)objc_msgSend之动态解析和消息转发
resolveMethod动态解析(动态决意)
在objc4源码中的lookUpImpOrForward
,有这么一段代码。
注释: 没有找到方法实现,尝试一次方法解析。
继续看里面resolveMethod_locked
的实现。
这里对cls进行了是否是元类的判断,如果是元类,说明是类方法的调用,则调用
resolveClassMethod
。如果是类,说明是对象方法调用,则调用resolveInstanceMethod
。
其中resolveClassMethod
resolveInstanceMethod
是通过msgSend去调用类方法resolveClassMethod:
或者resolveClassMethod:
。其中resolveCla ssMethod
中cls本来是元类
,通过getMaybeUnrealizedNonMetaClass
来获取元类对应的类对象
,然后再对类对象发送消息(相当于调用类方法)。
resolveClassMethod:
和resolveClassMethod:
的实现就交给了开发者,开发者可以再这里动态添加方法实现。
// 第一根稻草,使用动态解析,动态添加方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(methodOne)) {
Method methodNormal = class_getInstanceMethod(self, @selector(methodNormal));
class_addMethod(self,
@selector(methodOne),
method_getImplementation(methodNormal),
method_getTypeEncoding(methodNormal));
return YES
}
return [super resolveInstanceMethod:sel];
}
这样methodOne
的调用就可以使用methodNormal
的实现了。
消息转发
如果动态解析(动态决意)阶段还是没有对应的方法,那么就会来到消息转发阶段。消息转发阶段分为两部分,替换消息接收者阶段,也有叫快速转发,和完全消息转发阶段。
消息接收者替换
实现forwardingTargetForSelector
方法,并返回替换的对象。
//第二根稻草,使用快速消息转发,找其他对象来实现方法
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(methodTwo)) {
return self.forwardObject;
}
return nil;
}
这时候就会去_frowardObject
里面去找methodTwo
了。_frowardObject
的类实现如下:
@implementation OSMsgSendForwardObject
//从消息转发而来
- (void)methodTwo {
NSLog(@"%s__ %@",__FUNCTION__,[self class]);
}
@end
完全消息转发
如果forwardingTargetForSelector
方法返回的是nil,那么我们还有最后一根稻草可以抓住-完全消息转发。相比于快速抓发,不经可以替换消息接受者,还能替换方法。
//第三根稻草,使用完全消息转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(methodThree)) {
// NSMethodSignature *sig = [NSMethodSignature methodSignatureForSelector:aSelector];
NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:"v@:"];
return sig;
}
return nil;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
anInvocation.selector = @selector(methodNormal);
anInvocation.target = self.forwardObject;
[anInvocation invoke];
}
这里有两个类,NSMethodSignature
和 NSInvocation
。其中NSMethodSignature
是方法签名,可以通过方法的字符来实例化。NSInvocation
是方法调用实体,其中有target和selector和参数构成。详细可以看这里。
通过实现这两个方法就可以实现消息的完全转发。
总结与思考
在消息发送的时候,如果在
cache
和方法列表
中都没有找到方法实现,会来到动态解析阶段,通过调用resolveInstanseMethod
或resolveClassMethod
,来动态的解析方法的实现。如果在方法里返回NO,则会进入消息转发阶段,通过frowardingTargetSelector
,可以将消息转发到能处理消息的实例里面。如果没有进行快速转发,可以methodSignatureForSeletor
和 frowardInvocation
来进行完全消息转发。
那么问题来了,苹果为什么要设计这些阶段,难到真的是怕开发者忘实现方法吗?
首先,确实动态解析,快速转发和完全转发都能做到防止程序unrecognized selector sent to instance
类型的crash,这三种方式也被称做消息发送的三根救命稻草。。这些阶段的前提都是在没有找到方法实现,设计这些并不会影响消息发送的效率。而每根稻草实际的实现原理是不同的,动态解析是动态的去添加方法实现,消息转发是让一个替代者来实现。
参考链接
转载自:https://juejin.cn/post/7094940251419836430