iOS底层 消息转发
接上篇:iOS底层 消息查找流程
在lookUpImpOrForward方法中会看到这样一段代码,当在缓存和方法列表中未能找到相应的imp时,会调用这段代码尝试去解析。
// No implementation found. Try method resolver once.
if (resolver && !triedResolver) {
runtimeLock.unlock();
_class_resolveMethod(cls, sel, inst);
runtimeLock.lock();
// 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
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_resolveInstanceMethod,
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));
}
}
}
lookUpImpOrNil 查看imp是否为nil
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;
}
通过遍历父类再调用lookUpImpOrForward方法来查找是否能够解决此方法,如果还没有找到的话继续往下,还会调用_objc_msgForward_impcache,但是这个方法只有声明
#if !OBJC_OLD_DISPATCH_PROTOTYPES
extern void _objc_msgForward_impcache(void);
#else
extern id _objc_msgForward_impcache(id, SEL, ...);
#endif
_class_resolveInstanceMethod方法中的_objc_inform 处理软运行时错误(例如无法将类别添加到类)
/*
* this routine handles soft runtime errors...like not being able
* add a category to a class (because it wasn't linked in).
*/
void _objc_inform(const char *fmt, ...)
{
va_list ap;
char *buf1;
char *buf2;
va_start (ap,fmt);
vasprintf(&buf1, fmt, ap);
va_end (ap);
asprintf(&buf2, "objc[%d]: %s\n", getpid(), buf1);
_objc_syslog(buf2);
free(buf2);
free(buf1);
}
从源码入手好像进行不下去了,那我们用一个方法来看看程序在运行时,消息转发时会调用那些方法呢,这个方法是
// env NSObjCMessageLoggingEnabled
OBJC_EXPORT void
instrumentObjcMessageSends(BOOL flag)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
如下代码所示,实例了一个TWDemo类的对象,调用了一个只有声明没有实现的showSomething方法:
开启方法日志打印.png运行一下代码,运行时发送的所有消息都会打印到/private/tmp/msgSends-xxxx文件里了。
可以终端执行open /private/tmp/
打开日志文件,会看到如下信息
日志信息.png截屏2020-01-05下午10.31.15.png
看到当未找到方法时进入消息转发阶段会调用这些方法resolveInstanceMethod、
forwardingTargetForSelector、
methodSignatureForSelector
现在我们开启上帝视角:来一张消息转发流程图
在刚才的类里添加这些方法来打印相关信息和调用
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s -- %@",__func__, NSStringFromSelector(aSelector));
return [super methodSignatureForSelector:aSelector];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%s -- %@",__func__, NSStringFromSelector(sel));
return [super resolveInstanceMethod:sel];
}
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s -- %@",__func__, NSStringFromSelector(aSelector));
return [super forwardingTargetForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s ",__func__);
[super forwardInvocation:anInvocation];
}
因为还没有做签名转发处理,forwardInvocation这个方法目前是不会调用的,我们来看看打印:
2020-01-05 22:55:09.919730+0800 TWForwardDemo[1789:68385] Hello, World!
2020-01-05 22:55:09.920263+0800 TWForwardDemo[1789:68385] +[TWDemo resolveInstanceMethod:] -- showSomething
2020-01-05 22:55:09.920396+0800 TWForwardDemo[1789:68385] -[TWDemo forwardingTargetForSelector:] -- showSomething
2020-01-05 22:55:09.920516+0800 TWForwardDemo[1789:68385] -[TWDemo methodSignatureForSelector:] -- showSomething
2020-01-05 22:55:09.920661+0800 TWForwardDemo[1789:68385] +[TWDemo resolveInstanceMethod:] -- showSomething
2020-01-05 22:55:09.920749+0800 TWForwardDemo[1789:68385] -[TWDemo showSomething]: unrecognized selector sent to instance 0x1038005d0
2020-01-05 22:55:09.921661+0800 TWForwardDemo[1789:68385] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[TWDemo showSomething]: unrecognized selector sent to instance 0x1038005d0'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff33fb3d63 __exceptionPreprocess + 250
1 libobjc.A.dylib 0x00007fff69ea2bd4 objc_exception_throw + 48
2 CoreFoundation 0x00007fff3403e206 -[NSObject(NSObject) __retain_OA] + 0
3 CoreFoundation 0x00007fff33f5a51b ___forwarding___ + 1427
4 CoreFoundation 0x00007fff33f59ef8 _CF_forwarding_prep_0 + 120
5 TWForwardDemo 0x0000000100000b39 main + 137
6 libdyld.dylib 0x00007fff6b201405 start + 1
7 ??? 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)
调用了resolveInstanceMethod、forwardingTargetForSelector和methodSignatureForSelector方法后没找到解析方法后打印错误信息:unrecognized selector sent to instance,
既然开启了上帝视角,我们来处理一下方法的签名以及转发调用,
首先我们准备一个有实现的showSomething方法的新类TWDemo2,在TWDemo.m文件添加签名和转发调用。
首先我们直接在forwardingTargetForSelector方法里返回可以有实现方法的TWDemo2类的对象。
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s -- %@",__func__, NSStringFromSelector(aSelector));
if (aSelector == @selector(showSomething)) {
return [TWDemo2 alloc];
}
return [super forwardingTargetForSelector:aSelector];
}
运行代码,执行结果如下
2020-01-05 23:03:55.443885+0800 TWForwardDemo[1874:72744] Hello, World!
2020-01-05 23:03:55.444493+0800 TWForwardDemo[1874:72744] +[TWDemo resolveInstanceMethod:] -- showSomething
2020-01-05 23:03:55.444637+0800 TWForwardDemo[1874:72744] -[TWDemo forwardingTargetForSelector:] -- showSomething
2020-01-05 23:03:55.444681+0800 TWForwardDemo[1874:72744] -[TWDemo2 showSomething]
Program ended with exit code: 0
此时TWDemo2调用了showSomething方法,然后我们把刚刚的方法里面返回对象方法注释调用使用签名和转发的方式,
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s -- %@",__func__, NSStringFromSelector(aSelector));
if (aSelector == @selector(showSomething)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%s -- %@",__func__, NSStringFromSelector(sel));
return [super resolveInstanceMethod:sel];
}
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s -- %@",__func__, NSStringFromSelector(aSelector));
// if (aSelector == @selector(showSomething)) {
// return [TWDemo2 alloc];
// }
return [super forwardingTargetForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s ",__func__);
SEL aSelector = [anInvocation selector];
if ([[TWDemo2 alloc] respondsToSelector:aSelector]) {
[anInvocation invokeWithTarget:[TWDemo2 alloc]];
} else {
[super forwardInvocation:anInvocation];
}
}
打印结果如下:
2020-01-05 23:24:27.131806+0800 TWForwardDemo[2091:83719] Hello, World!
2020-01-05 23:24:27.132016+0800 TWForwardDemo[2091:83719] +[TWDemo resolveInstanceMethod:] -- showSomething
2020-01-05 23:24:27.132040+0800 TWForwardDemo[2091:83719] -[TWDemo forwardingTargetForSelector:] -- showSomething
2020-01-05 23:24:27.132055+0800 TWForwardDemo[2091:83719] -[TWDemo methodSignatureForSelector:] -- showSomething
2020-01-05 23:24:27.132072+0800 TWForwardDemo[2091:83719] +[TWDemo resolveInstanceMethod:] -- _forwardStackInvocation:
2020-01-05 23:24:27.132090+0800 TWForwardDemo[2091:83719] -[TWDemo forwardInvocation:]
2020-01-05 23:24:27.132104+0800 TWForwardDemo[2091:83719] -[TWDemo2 showSomething]
Program ended with exit code: 0
到此,消息转发成功。
分析:
1.未找到方法时会调用resolveInstanceMethod方法,这里允许用户为该类动态添加实现。如果实现了,则调用并返回。如果没有实现,继续2。
2.调用forwardingTargetForSelector方法,尝试查找一个能响应该该方法的对象。如果找到,返回该对象,由该对象处理这个方法。如果还是没有找到,继续3。
3.调用methodSignatureForSelector:方法,尝试获得当前方法的一个方法签名。如果获取不到,会抛出异常,如果获取到了会继续4。
4.调用forwardInvocation:方法,将地3步获取到的方法签名包装成Invocation传入,可以在这里调用 [anInvocation invokeWithTarget:[TWDemo2 alloc]]
传入可以处理的对象,如果还是没有处理会抛出异常。