在上篇文章方法查找流程通过在类和父类的缓存以及方法列表中进行查找,如果直到查找到NSObject
中都没有找到,然后会进行动态方法解析,我们先来看一下这一步做了什么?
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);
}
}
}
在这个方法中,通过判断我们不难判断出一个是对象方法解析,一个是类方法解析,因为我们知道对象方法是存在类中,类方法是存在元类中,所以! cls->isMetaClass()
这个条件满足肯定是对象方法解析了,我们先看一下对象方法解析。
- 对象方法动态解析
_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;
}
// 系统给你一次机会 - 你要不要针对 sel 来操作一下下
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
函数,传入元类cls->ISA()
,这里传入元类的原因是下面要查找SEL_resolveInstanceMethod
,也就是+ (BOOL)resolveInstanceMethod:(SEL)sel
这个方法,此方法在NSObject
中有实现,是一个类方法,类方法在元类中。 - 往下是执行
objc_msgSend
,就是在cls
中发送一个消息,是否实现+ (BOOL)resolveInstanceMethod:(SEL)sel
方法 - 如果实现则再次通过
lookUpImpOrNil
查找imp
-
imp
如果找到,则输出动态解析对象方法成功的日志 - 如果
imp
没有找到,则输出虽然实现了+(BOOL)resolveInstanceMethod:(SEL)sel
,并且返回了YES
,但并没有查找到imp
的日志
其实这里没有找到方法,也即是没有imp
,我们可以在+(BOOL)resolveInstanceMethod:(SEL)sel
这个方法中进行一步操作,也就是给我们要查找的方法一个已存在的imp
,这样就可以避免crash
,代码奉上:
+ (BOOL)resolveInstanceMethod:(SEL)sel{
// 获取到需要动态解析的方法名
if (sel == @selector(saySomething)) {
// 获取到需要动态解析到的方法sayHello的IMP和Method
IMP sayHIMP = class_getMethodImplementation(self, @selector(sayHello));
Method sayHMethod = class_getInstanceMethod(self, @selector(sayHello));
const char *sayHType = method_getTypeEncoding(sayHMethod);
// 通过API添加方法
return class_addMethod(self, sel, sayHIMP, sayHType);
}
return [super resolveInstanceMethod:sel];
}
- 类方法动态解析
_class_resolveClassMethod
:
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));
}
}
}
- 首先判断是否是元类,不是元类直接退出,因为类方法在元类中。
- 下面的执行跟对象方法的动态解析一样,把
SEL_resolveInstanceMethod
换成了SEL_resolveClassMethod
,容错处理代码:
+ (BOOL)resolveClassMethod:(SEL)sel{
if (sel == @selector(sayLove)) {
// 获取到元类中存储的类方法sayObjc
IMP sayHIMP = class_getMethodImplementation(objc_getMetaClass("LGStudent"), @selector(sayObjc));
Method sayHMethod = class_getClassMethod(objc_getMetaClass("LGStudent"), @selector(sayObjc));
const char *sayHType = method_getTypeEncoding(sayHMethod);
// 将类方法实现添加在元类之中
return class_addMethod(objc_getMetaClass("LGStudent"), sel, sayHIMP, sayHType);
}
return [super resolveClassMethod:sel];
}
如果在没有动态方法解析处理的情况下,调用了程序中没有的方法saySomething
或sayLove
,那么这个程序肯定会crash,但是如果实现了消息转发的处理,那么就可以将方法的调用转交其他的对象来处理,避免crash,来看一下消息转发。
消息转发
在消息的查找流程中有这样一段代码:
// Superclass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
就是方法日志的打印,调用了log_and_fill_cache
方法,我们看源码:
static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
if (objcMsgLogEnabled) {
bool cacheIt = logMessageSend(implementer->isMetaClass(),
cls->nameForLogging(),
implementer->nameForLogging(),
sel);
if (!cacheIt) return;
}
#endif
cache_fill (cls, sel, imp, receiver);
}
判断启用消息日志进入到logMessageSend
函数中:
spinlock_t objcMsgLogLock;
#if !SUPPORT_MESSAGE_LOGGING
void instrumentObjcMessageSends(BOOL flag)
{
}
#else
bool objcMsgLogEnabled = false;
static int objcMsgLogFD = -1;
bool logMessageSend(bool isClassMethod,
const char *objectsClass,
const char *implementingClass,
SEL selector)
{
char buf[ 1024 ];
// Create/open the log file
if (objcMsgLogFD == (-1))
{
snprintf (buf, sizeof(buf), "/tmp/msgSends-%d", (int) getpid ());
objcMsgLogFD = secure_open (buf, O_WRONLY | O_CREAT, geteuid());
if (objcMsgLogFD < 0) {
// no log file - disable logging
objcMsgLogEnabled = false;
objcMsgLogFD = -1;
return true;
}
}
// Make the log entry
snprintf(buf, sizeof(buf), "%c %s %s %s\n",
isClassMethod ? '+' : '-',
objectsClass,
implementingClass,
sel_getName(selector));
objcMsgLogLock.lock();
write (objcMsgLogFD, buf, strlen(buf));
objcMsgLogLock.unlock();
// Tell caller to not cache the method
return false;
}
void instrumentObjcMessageSends(BOOL flag)
{
bool enable = flag;
// Shortcut NOP
if (objcMsgLogEnabled == enable)
return;
// If enabling, flush all method caches so we get some traces
if (enable)
_objc_flush_caches(Nil);
// Sync our log file
if (objcMsgLogFD != -1)
fsync (objcMsgLogFD);
objcMsgLogEnabled = enable;
}
在这里我们可以看到日志的打印取决于objcMsgLogEnabled
,而instrumentObjcMessageSends
函数是赋值objcMsgLogEnabled
的,所以,我们可以在外面暴露打印日志,而日志存在于/tmp
这个文件夹下:
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGStudent *student = [LGStudent alloc] ;
// 动态方法决议
// 对象方法
// 类方法 -
instrumentObjcMessageSends(true);
[student saySomething];
instrumentObjcMessageSends(false);
}
return 0;
}
前往文件夹搜索/tmp
,可以看到msgSends-%d
这样的一个文件,打开:
+ LGStudent NSObject resolveInstanceMethod:
+ LGStudent NSObject resolveInstanceMethod:
- LGStudent NSObject forwardingTargetForSelector:
- LGStudent NSObject forwardingTargetForSelector:
- LGStudent NSObject methodSignatureForSelector:
- LGStudent NSObject methodSignatureForSelector:
- LGStudent NSObject class
- LGStudent NSObject doesNotRecognizeSelector:
- LGStudent NSObject doesNotRecognizeSelector:
- LGStudent NSObject class
可以看出在动态方法解析之后调用了forwardingTargetForSelector
消息快速转发流程
搜索一下源码,发现forwardingTargetForSelector
在NSObject
中有调用:
+ (id)forwardingTargetForSelector:(SEL)sel {
return nil;
}
这是不是什么也看不出来,来,我们看一下官方文档:
option
+command
选择
Show Quick Help
搜索
forwardingTargetForSelector
选择OC
我们只需要看
Discussion
中:(大家找到之后可以找翻译软件翻译,能看懂的受小弟膜拜)
- 该方法的返回对象是执行sel的新对象,也就是自己处理不了会将消息转发给别人的对象进行相关方法的处理,但是不能返回self,否则会一直找不到
- 该方法的效率较高,如果不实现或者nil,会走到forwardInvocation:方法进行处理
- 底层会调用objc_msgSend(forwardingTarget, sel, ...)来实现消息的发送
- 被转发消息的接受者参数和返回值等需要和原方法相同
具体实现以下:
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(saySomething)) {
return [LGTeacher alloc];
}
return [super forwardingTargetForSelector:aSelector];
}
表示saySomething
方法的处理被转发到LGTeacher
的相关类中实现。至此消息转发的快速流程结束,不crash
.
消息慢速转发流程
如果上述方法为nil
的时候,就会进入慢速转发流程,methodSignatureForSelector
进入此函数
// Replaced by CF (returns an NSMethodSignature)
+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)sel {
_objc_fatal("+[NSObject instanceMethodSignatureForSelector:] "
"not available without CoreFoundation");
}
// Replaced by CF (returns an NSMethodSignature)
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
_objc_fatal("+[NSObject methodSignatureForSelector:] "
"not available without CoreFoundation");
}
// Replaced by CF (returns an NSMethodSignature)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
_objc_fatal("-[NSObject methodSignatureForSelector:] "
"not available without CoreFoundation");
}
源码中也是什么都看不到,看官方文档(方法和快速转发流程一样样的)
这里可以看到执行
methodSignatureForSelector
还要创建一个NSInvocation
对象,这个又是什么东东,官方文档往下翻会有一个:
点击forwardInvocation
进入:
有点长,慢慢看,
- 实现
methodSignatureForSelector
还必须要实现forwardInvocation
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(saySomething)) { // v @ :
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
//
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s ",__func__);
SEL aSelector = [anInvocation selector];
if ([[LGTeacher alloc] respondsToSelector:aSelector])
[anInvocation invokeWithTarget:[LGTeacher alloc]];
else
[super forwardInvocation:anInvocation];
}
运行看打印:
2020-01-04 21:09:02.267016+0800 008-方法查找-消息转发[2475:201381] -[LGStudent methodSignatureForSelector:] -- saySomething
2020-01-04 21:09:02.267376+0800 008-方法查找-消息转发[2475:201381] -[LGStudent forwardInvocation:]
2020-01-04 21:09:02.267578+0800 008-方法查找-消息转发[2475:201381] -[LGTeacher saySomething]
先进入methodSignatureForSelector
然后到forwardInvocation
,最后查找到saySomething
消息无法处理
执行函数doesNotRecognizeSelector
,
+ (void)doesNotRecognizeSelector:(SEL)sel {
_objc_fatal("+[%s %s]: unrecognized selector sent to instance %p",
class_getName(self), sel_getName(sel), self);
}
报出错误信息
总结:
- 消息方法查找首先进行
MethodTableLookup
常规慢速查找,没有找到imp
- 首先进入动态方法解析
_class_resolveMethod
/_class_resolveInstanceMethod
,根据对象方法和类方法,进行不同的解析处理(当然,类方法也可以当做元类的对象方法进行解析) - 动态方法解析返回
NO
,则进入消息转发的快速阶段forwardingTargetForSelector
,也就是自己处理不了会将消息转发给别人的对象进行相关方法的处理 - 如果消息快速转发阶段未能处理,则进入到常规慢速转发流程
methodSignatureForSelector
,先针对aSelector
返回方法签名NSMethodSignature
,再去forwardInvocation
方法中集中处理