消息转发三部曲:
接上面消息发送,如果当前类和父类中都没有找到实现,那么就会开始尝试动态方法解析。
动态方法解析
//6.IMP没有找到,尝试方法解析一次
// No implementation found. Try method resolver once.
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
_class_resolveMethod(cls, sel, inst);
runtimeLock.read();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}
在执行了 _class_resolveMethod:
之后,会跳转到 retry
标签,重新执行查找方法实现的流程,只不过不会再调用 _class_resolveMethod:
方法了,因为通过 triedResolver
来判断是否进行该类是否进行过动态方法解析。如果首次走到这里,triedResolver = NO
,当动态方法解析进行过一次之后,会设置 triedResolver = YES
,这样下次走到这里的时候,就不会再次进行动态方法解析。
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
_class_resolveMethod
这个函数首先判断是否是 meta-class
类,如果不是元类,就执行 _class_resolveInstanceMethod
,如果是元类,执行 _class_resolveClassMethod
。
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
IMP imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
assert(cls->isMetaClass());
if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(_class_getNonMetaClass(cls, inst),
SEL_resolveClassMethod, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveClassMethod adds to self->ISA() a.k.a. cls
IMP imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
在 _class_resolveInstanceMethod
和 _class_resolveClassMethod
方法中,来查询是否已经在运行时将其动态插入类中的实现函数,如果没有重新调用 lookUpImpOrNil
并重新启动缓存,来判断是否已经添加上 sel
对应的 IMP
指针,并且重新触发 objc_msgSend
方法。
id objc_msgSend(id self, SEL _cmd, ...) {
Class class = object_getClass(self);
IMP imp = class_getMethodImplementation(class, _cmd);
return imp ? imp(self, _cmd, ...) : 0;
}
只要一提 objc_msgSend
,都会说它的伪代码如下或类似的逻辑,反正就是获取 IMP
并调用。
IMP class_getMethodImplementation(Class cls, SEL sel)
{
IMP imp;
if (!cls || !sel) return nil;
imp = lookUpImpOrNil(cls, sel, nil, YES/*initialize*/, YES/*cache*/, YES/*resolver*/);
// Translate forwarding function to C-callable external version
if (!imp) {
return _objc_msgForward;
}
return imp;
}
lookUpImpOrNil
函数获取不到 IMP
时就返回 _objc_msgForward
。
IMP lookUpImpOrNil(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
if (imp == _objc_msgForward_impcache) return nil;
else return imp;
}
lookUpImpOrNil
方法判断返回 imp
结果是否和 _objc_msgForward_impcache
相同,如果相同返回 nil
,反之返回 imp
。
回到 lookUpImpOrForward
方法中,如果也没有找到 imp
的实现,那么method resolver
也没用了,只能进入消息转发阶段。进入这个阶段之前,imp
变成 _objc_msgForward_impcache
,最后再加入缓存中。
当一个方法没有实现时,可以通过重写 resolveInstanceMethod:
和 resolveClassMethod:
方法,动态添加未实现的方法。其中第一个是添加实例方法,第二个是添加类方法。这两个方法都有一个 BOOL
返回值,返回 NO
则进入消息转发机制。
void dynamicMethodIMP(id self, SEL _cmd) {
// implementation ....
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(resolveThisMethodDynamically)) {
class_addMethod([self class], sel, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
objc_msgForward
STATIC_ENTRY __objc_msgForward_impcache
// Method cache version
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band condition register is NE for stret, EQ otherwise.
MESSENGER_START
nop
MESSENGER_END_SLOW
jne __objc_msgForward_stret
//1.跳转到__objc_msgForward
jmp __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
// Non-stret version
//2.执行__objc_forward_handler
movq __objc_forward_handler(%rip), %r11
jmp *%r11
END_ENTRY __objc_msgForward
ENTRY __objc_msgForward_stret
// Struct-return version
movq __objc_forward_stret_handler(%rip), %r11
jmp *%r11
END_ENTRY __objc_msgForward_stret
在执行 _objc_msgForward
之后会调用 __objc_forward_handler
函数。
__attribute__((noreturn)) void
objc_defaultForwardHandler(id self, SEL sel)
{
_objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
"(no message forward handler is installed)",
class_isMetaClass(object_getClass(self)) ? '+' : '-',
object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
看源码实现当我们给一个对象发送一个没有实现的方法的时候,如果其父类也没有这个方法,则会崩溃,报错信息类似于这样:unrecognized selector sent to instance
,然后接着会跳出一些堆栈信息,这些信息就是从这里而来。
重定向
- (id)forwardingTargetForSelector:(SEL)aSelector
当动态方法解析不作处理返回 NO
时,消息转发机制会被触发。在这时forwardInvocation:
方法会被执行。
在消息转发机制执行前,Runtime
系统会再给我们一次偷梁换柱的机会,即通过重载 - (id)forwardingTargetForSelector:(SEL)aSelector
方法替换消息的接受者为其他对象:
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if(aSelector == @selector(mysteriousMethod:)){
return alternateObject;
}
return [super forwardingTargetForSelector:aSelector];
}
转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
当 forwardingTargetForSelector:
方法未做出任何响应的话,会来到消息转发流程。消息转发时会首先调用 methodSignatureForSelector:
方法,在方法内部生成 NSMethodSignature
类型的方法签名对象。在生成签名对象时,可以指定 target
和 SEL
,可以将这两个参数换成其他参数,将消息转发给其他对象。
[otherObject methodSignatureForSelector:otherSelector];
生成 NSMethodSignature
签名对象后,就会调用 forwardInvocation:
方法,这是消息转发中最后一步了,如果在这步还没有对消息进行处理,则会导致崩溃。
该消息的唯一参数是个 NSInvocation
类型的对象,该对象封装了原始的消息和消息的参数。我们可以实现 forwardInvocation:
方法来对不能处理的消息做一些默认的处理,也可以将消息转发给其他对象来处理,而不抛出错误。
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if ([object respondsToSelector:[anInvocation selector]]) {
[anInvocation invokeWithTarget:object];
} else {
[super forwardInvocation:anInvocation];
}
}
这里需要注意的是参数 anInvocation
是从哪的来的呢?其实在 forwardInvocation:
消息发送前,Runtime
系统会向对象发送 methodSignatureForSelector:
消息,并取到返回的方法签名用于生成 NSInvocation
对象。所以我们在重写 forwardInvocation:
的同时也要重写 methodSignatureForSelector:
方法,否则会抛异常。
当一个对象由于没有相应的方法实现而无法响应某消息时,运行时系统将通过 forwardInvocation:
消息通知该对象。每个对象都从 NSObject
类中继承了 forwardInvocation:
方法。然而,NSObject
中的方法实现只是简单地调用了 doesNotRecognizeSelector:
。通过实现我们自己的 forwardInvocation:
方法,我们可以在该方法实现中将消息转发给其它对象。
forwardInvocation:
方法就像一个不能识别的消息的分发中心,将这些消息转发给不同接收对象。或者它也可以象一个运输站将所有的消息都发送给同一个接收对象。它可以将一个消息翻译成另外一个消息,或者简单的”吃掉“某些消息,因此没有响应也没有错误。
forwardInvocation:
方法也可以对不同的消息提供同样的响应,这一切都取决于方法的具体实现。该方法所提供是将不同的对象链接到消息链的能力。
注意:
forwardInvocation:
方法只有在消息接收对象中无法正常响应消息时才会被调用。 所以,如果我们希望一个对象将 negotiate
消息转发给其它对象,则这个对象不能有 negotiate
方法。否则,forwardInvocation:
将不可能会被调用。
实战
1、动态解析
我们在Car
类的.m
文件里面,通过上面介绍动态解析可以知道,可以重载resolveInstanceMethod:
和resolveClassMethod:
方法分别添加实例方法实现和类方法实现。因为当Runtime
系统在Cache
和方法分发表中找不到要执行的方法时,Runtime
会调用resolveInstanceMethod:
或resolveClassMethod:
来给程序员一次动态添加方法实现的机会。
2、重定向
我们新建一个Person
类,为了让运行时系统能够运行到forwardingTargetForSelector:
方法,我们先在resolveInstanceMethod:
中返回NO
,代码如下:
从运行结果中看出,我们执行[person fly]
方法,控制台中打出Car
的run
方法,最终也实现了消息的转发。
Person *person = [[Person alloc] init];
[person fly];
3、转发
如果我们都不实现forwardingTargetForSelector
,系统就会方法methodSignatureForSelector
和forwardInvocation
来实现转发,代码如下:
从运行结果中看出,我们执行[person fly]
方法,控制台中打出Car
的run
方法,最终也实现了消息的转发。
注意:
-
methodSignatureForSelector
用来生成方法签名,这个签名就是给forwardInvocation
中的参数NSInvocation
调用的。 -
unrecognized selector sent to instance
,原来就是因为methodSignatureForSelector
这个方法中,由于没有找到fly
对应的实现方法,所以返回了一个空的方法签名,最终导致程序报错崩溃。
以上就是消息的转发,如果有觉得上述我讲的不对的地方欢迎指出,大家多多交流沟通。