前言
前文介绍的方法的查找流程:快速查找和慢速慢速查找流程。但是并没有讲当方法实现没有找到时,系统是如何处理的,本文将介绍这一部分。
1. 方法决议
if (slowpath(behavior & LOOKUP_RESOLVER)) {
//LOOKUP_RESOLVER = 2 behavior = 3
//behavior 异或为1
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
当慢速查找没有找到时,进入方法决议阶段——resolveMethod_locked
。
在进入resolveMethod_locked
查看方法决议过程前,先提前了解几个重要方法的实现和作用。
1.1 resolveInstanceMethod:和 resolveClassMethod:
NSObject.h
+ (BOOL)resolveClassMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
+ (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
NSObject.mm
+ (BOOL)resolveClassMethod:(SEL)sel {
return NO;
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return NO;
}
resolveClassMethod:
为类方法的给定选择器提供动态实现。
resolveInstanceMethod:
为实例方法的给定选择器提供动态实现。
这两个类方法在基类NSObject
中声明和实现,目的是当在慢速查找imp
时,如果没有找到imp
,可以当前的sel
动态的添加方法实现。默认返回NO
。
子类可以重写这个方法,为指定的sel
添加动态实现,然后返回YES
,表示进行了这个方法的动态决议。
以官方提供的例子为例:
Objective-C
方法只是一个 C
函数,它至少接受两个参数——self和_cmd。使用该函数,可以将函数作为方法添加到类中。
void dynamicMethodIMP(id self, SEL _cmd)
{
// implementation ....
}
给定以下函数:class_addMethod
,可以使用它动态地将dynamicMethodIMP
作为方法(称为)添加到类中,如下所示:resolveInstanceMethod:resolveThisMethodDynamically
+ (BOOL) resolveInstanceMethod:(SEL)aSEL
{
if (aSEL == @selector(resolveThisMethodDynamically))
{
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:aSel];
}
1.2 resolveInstanceMethod/resolveClassMethod
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
//注册resolveInstanceMethod:方法签名 为调用方法准备
//resolveInstanceMethod: 是一个类方法, 可在自定义实现, 进行方法的决议
//
SEL resolve_sel = @selector(resolveInstanceMethod:);
//判断resolveInstanceMethod是否已实现 没有就直接return
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
// Resolver not implemented.
return;
}
//调用resolveInstanceMethod 方法
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
// 查找sel的imp, imp可能为空
// lookUpImpOrNilTryCache执行过程中会调用lookUpImpOrForward,
// 如果经过方法决议后, 有了sel的方法实现imp, 经过查找, 会将imp 存入缓存, 以后方法决议就不会再触发了
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
// 输出记录
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));
}
}
}
resolveInstanceMethod
主要分为两部分,调用resolveInstanceMethod:
方法进行方法决议,然后进行一次imp
查找。
lookUpImpOrNilTryCache
可能会返回空值 nil。这里执行结果imp, 并不会返回,只是输出记录的时候使用一下。但是找到的imp
(不管是真正的方法实现,还是forward_imp
)会被缓存起来,后续在查找时,直接在缓存中就可以找到了。
有时候resolveInstanceMethod:
可能返回YES,但是并没有真正的添加方法实现。所以经过一次查找之后,可以最终确认一下。然后将决议结果记录输出。
resolveClassMethod
参照resolveInstanceMethod
即可。
1.3 lookUpImpOrNilTryCache/lookUpImpOrForwardTryCache
lookUpImpOrNilTryCache
, 查找imp, 找不到会返回nil。behavior | LOOKUP_NIL
使得behavior & LOOKUP_NIL
结果为真。具体判断参见lookUpImpOrForward
的结尾部分。
IMP lookUpImpOrNilTryCache(id inst, SEL sel, Class cls, int behavior)
{
//behavior | LOOKUP_NIL = 7
return _lookUpImpTryCache(inst, sel, cls, behavior | LOOKUP_NIL);
}
lookUpImpOrForwardTryCache
,查找imp, 找不回会返回一个默认的方法实现 _objc_msgForward_impcache
。
IMP lookUpImpOrForwardTryCache(id inst, SEL sel, Class cls, int behavior)
{
return _lookUpImpTryCache(inst, sel, cls, behavior);
}
1.4 _lookUpImpTryCache
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
// behavior = 1
runtimeLock.assertUnlocked();
if (slowpath(!cls->isInitialized())) {
// see comment in lookUpImpOrForward
// cls未初始化, 去lookUpImpOrForward查找sel的imp,
// 此时behavior = 1 不会再进行方法决议
return lookUpImpOrForward(inst, sel, cls, behavior);
}
//从缓存中查找 快速查找
IMP imp = cache_getImp(cls, sel);
//imp 不为空 直接返回
if (imp != NULL) goto done;
#if CONFIG_USE_PREOPT_CACHES
if (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) {
imp = cache_getImp(cls->cache.preoptFallbackClass(), sel);
}
#endif
//imp为空, 进行慢速查找 并将结果返回
if (slowpath(imp == NULL)) {
return lookUpImpOrForward(inst, sel, cls, behavior);
}
//返回imp 或 nil
done:
if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
return nil;
}
return imp;
}
_lookUpImpTryCache
是一个标准的imp
查找方法。先去查找缓存cache_getImp
,找不到就去进行慢速查找lookUpImpOrForward
。
注意,如果是在方法慢速查找过程中执行_lookUpImpTryCache
,说明第一遍的慢速查找没有找到,进行了方法决议,方法决议后再次查找。如果执行到lookUpImpOrForward
,这已经是第二次执行它了,behavior
已经与LOOKUP_RESOLVER(值为2)执行过异或了, 与LOOKUP_RESOLVER(2)做与运算不会再为真了,所以不可能在进行一次方法决议了。
1.5 resolveMethod_locked
经过前面几个方法介绍,再来看resolveMethod_locked
方法决议流程就清楚多了。
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
//behavior=1
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
if (! cls->isMetaClass()) {
//cls不是元类 inst为实例对象 sel为实例方法
//进行实例方法决议
// try [cls resolveInstanceMethod:sel]
resolveInstanceMethod(inst, sel, cls);
}
else {
//cls是元类 inst是类对象 sel是类方法
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
//先进行类方法决议
resolveClassMethod(inst, sel, cls);
//调用lookUpImpOrNilTryCache 判断经过类方法决议之后, sel是否已经有了对应的imp
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
//如果仍然没有找到imp, 再进行一次实例方法决议
//为什么可以再进行一次实例方法决议, 通过isa走位图, 根元类的父类是 NSObject类, NSObject类里存储的实例方法, 所以可以再进行一次实例方法决议.
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
// 进行方法决议之后, 可能为sel添加了方法实现imp, 因此再进行一次方法查找
// 此时的behavior=1 即使还找到imp也不会再进行方法决议了, 而是返回imp(可能不是真正的imp, 而是_objc_msgForward_impcache)
// 将结果返回
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
resolveMethod_locked
会对当前的cls
进行判断,执行不同的方法决议流程。
如果cls
不是元类,那么当前inst
为实例对象 sel
为实例方法,执行实例方法决议流程。
如果cls
是元类,那么当前inst
是类对象 sel
是类方法,执行类方法决议流程:
- 先进行类方法决议
resolveClassMethod
- 然后查找
imp
, 判断经过类方法决议之后, sel是否已经有了对应的imp - 如果仍然没有找到imp, 再进行一次实例方法决议
为什么可以再进行一次实例方法决议? 通过isa走位图可以, 根元类的父类是 NSObject类。 NSObject类里存储的实例方法, 加入在NSObject的实例决议方法,所以可以再进行一次实例方法决议.
最后,会调用lookUpImpOrForwardTryCache
,进行imp
的查找并将结果返回(可能是真正的imp, 也可能是_objc_msgForward_impcache
)。
我们已经知道在前面执行
resolveInstanceMethod(inst, sel, cls);
或者
resolveClassMethod(inst, sel, cls);
时,方法内部已经进行了一次imp查找。这次执行lookUpImpOrForwardTryCache
在缓存中就会找到imp
了。
1.6 举例
说了这么多,举个例子测试一下。
定义ZPerson
类,声明sayHello
和sayNB
方法,但是只实现了sayNB
方法。
同时,重写方法resolveInstanceMethod
,添加打印,以确认找不到imp
时,会进入方法决议。
@interface ZPerson : NSObject
- (void)sayHello;
- (void)sayNB;
@end
@implementation ZPerson
//- (void)sayHello{
// NSLog(@"你好");
//}
- (void)sayNB{
NSLog(@"你牛逼");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"进入实例方法动态决议");
return [super resolveInstanceMethod:sel];
}
@end
创建ZPerson
实例,并执行sayHello
方法。由于没有提供sayHello
的实现和+resolveInstanceMethod:
的实现,不出意外会崩溃。
ZPerson *person = [ZPerson alloc];
[person sayHello];
执行结果:
不出所料,由于没有找到真正的方法实现,程序崩溃了(2)。
同时在输出错误信息之前,执行了+resolveInstanceMethod:
(1)。
问题一:我们注意到
+resolveInstanceMethod:
执行了两次,这是为什么呢,我们将在后面介绍。
下面我们在+resolveInstanceMethod:
方法中为sayHello
添加动态方法实现:
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(sayHello)) {
NSLog(@"执行 sayHello 方法决议");
IMP sayHelloIMP = class_getMethodImplementation(self, @selector(sayNB));
Method sayHelloMethod = class_getInstanceMethod(self, @selector(sayNB));
const char *sayHelloType = method_getTypeEncoding(sayHelloMethod);
return class_addMethod(self, sel, sayHelloIMP, sayHelloType);
}
return [super resolveInstanceMethod:sel];
}
在决议方法中,动态为sayHello
提供了实现,将sayNB
的实现imp
和sayHello
的sel
绑定到一起。
进入实例方法动态决议: sayHello
执行 sayHello 的方法决议
你牛逼
不会崩溃了。会转而执行sayNB
方法。
对于崩溃的一点思考: 既然可以在
resolveInstanceMethod:
中为方法添加动态实现,那么以后是不是就可以避免此类问题产生的崩溃了呢?这是可以的。我们可以在NSObject
的分类中重写此方法,来统一处理。而且不管是实例方法
还是类方法
,都可以在resolveInstanceMethod:
中动态添加实现。 但是也有一些缺陷。当系统方法可能会被更改,可以通过为自定义方法添加前缀来解决。这种方式的侵入性比较强,如果子类中也重写了此方法,就不会执行NSObject
的方法了。
2. 消息转发
2.1 objcMsgLogEnabled
继续思考前面提出的问题,当在resolveInstanceMethod:
中没有正确处理sel
时,会执行两次该方法,但是我们完整看完整个方法决议的过程,并没有找到第二次执行方法决议时机。我们猜测可能是在系统处理崩前,做了一些操作。
查看崩溃堆栈:
我们发现,从main
中出现异常,到动态库抛出异常objc_exception_throw + 48
,中间经过了CoreFoundation
库的处理。但是CoreFoundation
是非开源的,我们暂时还不能知道具体发生了啥。
但是我们在前面方法慢速查找的过程中,知道可以输出查找imp
的日志到文件中,我们试试,能不能找到一些线索。
instrumentObjcMessageSends(true);
[person sayHello];
在调用sayHello
之前,打开objcMsgLogEnabled
。
然后对应日志文件,查看。
+ ZPerson NSObject resolveInstanceMethod:
+ ZPerson NSObject resolveInstanceMethod:
- ZPerson NSObject forwardingTargetForSelector:
- ZPerson NSObject forwardingTargetForSelector:
- ZPerson NSObject methodSignatureForSelector:
- ZPerson NSObject methodSignatureForSelector:
+ ZPerson NSObject resolveInstanceMethod:
+ ZPerson NSObject resolveInstanceMethod:
- ZPerson NSObject doesNotRecognizeSelector:
- ZPerson NSObject doesNotRecognizeSelector:
找到如下方法:
resolveInstanceMethod:
forwardingTargetForSelector:
methodSignatureForSelector:
doesNotRecognizeSelector:
之所以都是两个,是因为这些方法在执行前都会查找方法存在不存在,执行慢速查找时,记录一次。由于不会缓存,所以真正执行时,又执行一次慢速查找,又记录一次,每次上面的方法都会有两次记录。
我们看到在methodSignatureForSelector:
之后,doesNotRecognizeSelector:
方法之前,又执行了一次方法决议resolveInstanceMethod:
。是不是可以猜测,系统报错之前,苹果又给了一次机会,进行方法查找呢?请继续看
下面根据记录的方法顺序,来依次查看方法。
2.2 forwardingTargetForSelector
在objc
源码中搜索forwardingTargetForSelector
发现只在NSObject
中提供了定义和默认实现,没有找到更多信息。
但是没事,既然是OC
方法,我们先去官方文档找一找。
通过文档,大概知道此方法可返回一个对象,此对象用来接收处理传入的方法sel
。也可以返回nil
,但是不能返回自身(self
)那样会陷入死循环。
NSObeject
中默认实现:
- (id)forwardingTargetForSelector:(SEL)sel {
return nil;
}
如果只想将消息重定向到另一个对象时,可以使用此方法,并且可以比常规转发快一个数量级,所以称为快速转发。如果消息转发的目的是捕获 NSInvocation
或在转发期间操作参数或返回值,就不能使用它了。可以使用forwardInvocation:
,慢速转发。
我们尝试使用一下:
定义ZStudent
类,并实现sayHello
方法。
@interface ZStudent : NSObject
- (void)sayHello;
@end
- (void)sayHello{
NSLog(@"学生说: 你好");
}
在ZPerson
类中重写forwardingTargetForSelector:
方法,将sayHello
消息,交给一个ZStudent
对象处理。
注意:将上面的方法决议注释掉。
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"快速转发 forwardingTargetForSelector: %@", NSStringFromSelector(aSelector));
if (aSelector == @selector(sayHello)) {
return [ZStudent alloc];
}
return [super forwardingTargetForSelector:aSelector];
}
执行结果:
进入实例方法动态决议: sayHello
快速转发 forwardingTargetForSelector: sayHello
学生说: 你好
程序没有崩溃,也在进入方法的快速转发之前,执行了一次方法决议。
这就是消息的快速转发,可以将当前对象不能处理的消息,交给一个能处理这个消息的对象来处理。
如果没有实现forwardingTargetForSelector
方法快速转发或者没有没传入的SEL
返回一个替代的消息接受者,系统将进入慢速消息转发
流程。
2.3 forwardInvocation
当一个对象收到一条没有相应方法实现的消息时,运行时系统会给接收者一个机会——将消息委托给另一个接收者处理。它通过创建一个NSInvocation
对象来包装需要委托的消息,NSInvocation
包含一个消息接收者target
,一个方法选择器sel
,消息的参数和返回值。NSInvocation
对象作为参数,传入接收者的forwardInvocation
方法,来将将消息转发给另一个对象。(如果该对象也无法响应消息,它也将有机会转发它。)
NSObject
的默认实现:
+ (void)forwardInvocation:(NSInvocation *)invocation {
[self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)];
}
默认调用doesNotRecognizeSelector:
。
doesNotRecognizeSelector:
的默认实现在Core Foundation
中。
// Replaced by CF (throws an NSException)
- (void)doesNotRecognizeSelector:(SEL)sel {
_objc_fatal("-[%s %s]: unrecognized selector sent to instance %p",
object_getClassName(self), sel_getName(sel), self);
}
如果子类中实现forwardInvocation:
就像下面这样:
- (void)forwardInvocation:(NSInvocation *)invocation
{
SEL aSelector = [invocation selector];
if ([friend respondsToSelector:aSelector])
[invocation invokeWithTarget:friend];
else
[super forwardInvocation:invocation];
}
注意1:
除了实现forwardInvocation:
方法之外,还需要重写methodSignatureForSelector:
方法。系统要想获得创建NSInvocation
对象需要的信息,需要methodSignatureForSelector:
方法为给定的方法选择器SEL
提供一个方法签名。
methodSignatureForSelector:
也可以返回nil
,这样就不会执行forwardInvocation:
了。
注意2:
NSObject
提供的默认实现只是调用方法;它不转发任何消息。因此,如果您选择不实现forwardInvocation:
,向对象发送无法识别的消息将引发异常,从而会执行doesNotRecognizeSelector:
方法。
2.4 methodSignatureForSelector
methodSignatureForSelector:
方法,会返回一个NSMethodSignature
对象,该对象包含了给定选择器SEL
的方法的描述。如果方法没有找到也可以返回nil
。
methodSignatureForSelector:
的默认实现在Core Foundation
中
// Replaced by CF (returns an NSMethodSignature)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
_objc_fatal("-[NSObject methodSignatureForSelector:] "
"not available without CoreFoundation");
}
下面看一下慢速消息转发的实现流程。
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"快速转发 forwardingTargetForSelector: %@", NSStringFromSelector(aSelector));
// if (aSelector == @selector(sayHello)) {
// return [ZStudent alloc];
// }
return [super forwardingTargetForSelector:aSelector];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"慢速转发 methodSignatureForSelector: %@", NSStringFromSelector(aSelector));
if (aSelector == @selector(sayHello)) {
NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:"];
return methodSignature;
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"慢速转发 forwardInvocation: %@", anInvocation);
if (anInvocation.selector == @selector(sayHello)) {
[anInvocation invokeWithTarget:[ZStudent alloc]];
} else {
[super forwardInvocation:anInvocation];
}
}
首先将前面快速转发的处理过程注释。
再将@selector(sayHello)
封装成NSMethodSignature
返回
最后在forwardInvocation:
方法中,将ZPSerson
对象的sayHello
方法转发给了ZStudent
对象来执行。
结果:
进入实例方法动态决议: sayHello
快速转发 forwardingTargetForSelector: sayHello
慢速转发 methodSignatureForSelector: sayHello
进入实例方法动态决议: _forwardStackInvocation:
慢速转发 forwardInvocation: <NSInvocation: 0x10138b5a0>
学生说: 你好
结果正如我们预想的一样,调用了ZPSerson
对象的sayHello
方法,但是由于没有实现,从而转发给了ZZStudent
对象来执行。
问题二:我们看到,在消息转发之前,方法签名
methodSignatureForSelector:
之后,进行消息转发forwardInvocation:
之前,又执行了一次方法决议,但是这次决议的方法变成_forwardStackInvocation:
了。这又是为什么呢?
带着问题一和问题二,进入下面的汇编分析。
3. 转发流程的汇编分析
-
找到
CoreFoundation.framework
:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/CoreFoundation.framework
-
打开
hoper
, 将CoreFoundation
的可执行文件拖入。 -
由于是在mac上分析,选择
x86 64
架构。
4.搜索____forwarding___
5.window
->Show Pseudo Code of Procedure
查看伪代码:
int ____forwarding___(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) {
r9 = arg5;
r8 = arg4;
rcx = arg3;
r13 = arg1;
r15 = arg0;
rax = COND_BYTE_SET(NE);
if (arg1 != 0x0) {
r12 = *_objc_msgSend_stret;
}
else {
r12 = *_objc_msgSend;
}
rbx = *(r15 + rax * 0x8);
rsi = *(r15 + rax * 0x8 + 0x8);
var_140 = rax * 0x8;
if (rbx >= 0x0) goto loc_115af7;
loc_115ac0:
rax = *_objc_debug_taggedpointer_obfuscator;
rax = *rax;
rcx = (rax ^ rbx) >> 0x3c & 0x7;
rax = ((rax ^ rbx) >> 0x34 & 0xff) + 0x8;
if (rcx != 0x7) {
rax = rcx;
}
if (rax == 0x0) goto loc_115ea6;
loc_115af7:
var_150 = r12;
var_138 = rsi;
var_148 = r15;
rax = object_getClass(rbx);
r15 = rax;
r12 = class_getName(rax);
if (class_respondsToSelector(r15, @selector(forwardingTargetForSelector:)) == 0x0) goto loc_115bab;
loc_115b38:
rax = [rbx forwardingTargetForSelector:var_138];
if ((rax == 0x0) || (rax == rbx)) goto loc_115bab;
loc_115b55:
if (rax >= 0x0) goto loc_115b91;
loc_115b5a:
rcx = *_objc_debug_taggedpointer_obfuscator;
rcx = *rcx;
rdx = (rcx ^ rax) >> 0x3c & 0x7;
rcx = ((rcx ^ rax) >> 0x34 & 0xff) + 0x8;
if (rdx != 0x7) {
rcx = rdx;
}
if (rcx == 0x0) goto loc_115e95;
loc_115b91:
*(var_148 + var_140) = rax;
r15 = 0x0;
goto loc_115ef1;
loc_115ef1:
if (**___stack_chk_guard == **___stack_chk_guard) {
rax = r15;
}
else {
rax = __stack_chk_fail();
}
return rax;
loc_115e95:
rbx = rax;
r15 = var_148;
r12 = var_150;
goto loc_115ea6;
loc_115ea6:
if (dyld_program_sdk_at_least(0x7e30901ffffffff) != 0x0) goto loc_116040;
loc_115ebd:
r14 = _getAtomTarget(rbx);
*(r15 + var_140) = r14;
___invoking___(r12, r15, r15, 0x400, 0x0, r9, var_150, var_148, var_140, var_138, var_130, stack[-304], stack[-296], stack[-288], stack[-280], stack[-272], stack[-264], stack[-256], stack[-248], stack[-240]);
if (*r15 == r14) {
*r15 = rbx;
}
goto loc_115ef1;
loc_116040:
____forwarding___.cold.1();
rax = objc_opt_class(@class(NSInvocation));
*____forwarding___.invClass = rax;
rax = class_getInstanceSize(rax);
*____forwarding___.invClassSize = rax;
return rax;
loc_115bab:
var_140 = rbx;
if (strncmp(r12, "_NSZombie_", 0xa) == 0x0) goto loc_115f30;
loc_115bce:
r14 = var_140;
if (class_respondsToSelector(r15, @selector(methodSignatureForSelector:)) == 0x0) goto loc_115f46;
loc_115bef:
rbx = var_138;
rax = [r14 methodSignatureForSelector:rbx];
if (rax == 0x0) goto loc_115fc1;
loc_115c0e:
r15 = rax;
rax = [rax _frameDescriptor];
r12 = rax;
if (((*(int16_t *)(*rax + 0x22) & 0xffff) >> 0x6 & 0x1) != r13) {
rax = sel_getName(rbx);
rcx = "";
if ((*(int16_t *)(*r12 + 0x22) & 0xffff & 0x40) == 0x0) {
rcx = " not";
}
r8 = "";
if (r13 == 0x0) {
r8 = " not";
}
_CFLog(0x4, @"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'. Signature thinks it does%s return a struct, and compiler thinks it does%s.", rax, rcx, r8, r9, var_150);
}
if (class_respondsToSelector(object_getClass(r14), @selector(_forwardStackInvocation:)) == 0x0) goto loc_115d61;
loc_115c9a:
if (*____forwarding___.onceToken != 0xffffffffffffffff) {
dispatch_once(____forwarding___.onceToken, ^ {/* block implemented at ______forwarding____block_invoke */ } });
}
[NSInvocation requiredStackSizeForSignature:r15];
var_138 = r15;
rdx = *____forwarding___.invClassSize;
r13 = &var_150 - (rdx + 0xf & 0xfffffffffffffff0);
memset(r13, 0x0, rdx);
objc_constructInstance(*____forwarding___.invClass, r13);
var_150 = rax;
r15 = var_138;
[r13 _initWithMethodSignature:var_138 frame:var_148 buffer:&stack[-8] - (0xf + rax & 0xfffffffffffffff0) size:rax];
[var_140 _forwardStackInvocation:r13];
rbx = 0x1;
goto loc_115dce;
loc_115dce:
if (*(int8_t *)(r13 + 0x34) != 0x0) {
rax = *r12;
if (*(int8_t *)(rax + 0x22) < 0x0) {
rcx = *(int32_t *)(rax + 0x1c);
rdx = *(int8_t *)(rax + 0x20) & 0xff;
memmove(*(rdx + var_148 + rcx), *(rdx + rcx + *(r13 + 0x8)), *(int32_t *)(*rax + 0x10));
}
}
rax = [r15 methodReturnType];
r14 = rax;
rax = *(int8_t *)rax;
if ((rax != 0x76) && (((rax != 0x56) || (*(int8_t *)(r14 + 0x1) != 0x76)))) {
r15 = *(r13 + 0x10);
if (rbx != 0x0) {
r15 = [[NSData dataWithBytes:r15 length:var_150] bytes];
[r13 release];
rax = *(int8_t *)r14;
}
if (rax == 0x44) {
asm { fld tword [r15] };
}
}
else {
r15 = ____forwarding___.placeholder;
if (rbx != 0x0) {
r15 = ____forwarding___.placeholder;
[r13 release];
}
}
goto loc_115ef1;
loc_115d61:
var_138 = r12;
r12 = r14;
if (class_respondsToSelector(object_getClass(r14), @selector(forwardInvocation:)) == 0x0) goto loc_115f8e;
loc_115d8d:
rax = [NSInvocation _invocationWithMethodSignature:r15 frame:var_148];
r13 = rax;
[r12 forwardInvocation:rax];
var_150 = 0x0;
rbx = 0x0;
r12 = var_138;
goto loc_115dce;
loc_115f8e:
r14 = @selector(forwardInvocation:);
____forwarding___.cold.4(&var_130, r12);
rcx = r14;
_CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- trouble ahead", var_140, rcx, r8, r9, var_150);
goto loc_115fba;
loc_115fba:
rbx = var_138;
goto loc_115fc1;
loc_115fc1:
rax = sel_getName(rbx);
r14 = rax;
rax = sel_getUid(rax);
if (rax != rbx) {
rcx = r14;
r8 = rax;
_CFLog(0x4, @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort", var_138, rcx, r8, r9, var_150);
}
if (class_respondsToSelector(object_getClass(var_140), @selector(doesNotRecognizeSelector:)) == 0x0) goto loc_116034;
loc_11601b:
[var_140 doesNotRecognizeSelector:rdx];
asm { ud2 };
rax = loc_116034(rdi, rsi, rdx, rcx, r8, r9);
return rax;
loc_116034:
____forwarding___.cold.3(var_140);
goto loc_116040;
loc_115f46:
rbx = class_getSuperclass(r15);
r14 = object_getClassName(r14);
if (rbx == 0x0) {
rax = object_getClassName(var_140);
rcx = r14;
r8 = rax;
_CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- did you forget to declare the superclass of '%s'?", var_140, rcx, r8, r9, var_150);
}
else {
rcx = r14;
_CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- trouble ahead", var_140, rcx, r8, r9, var_150);
}
goto loc_115fba;
loc_115f30:
r14 = @selector(forwardingTargetForSelector:);
____forwarding___.cold.2(var_140, r12, var_138, rcx, r8);
goto loc_115f46;
}
3.1 forwardingTargetForSelector:
进入____forwarding___
,会先进行快速转发。
过程1:调用class_respondsToSelector
方法。查找forwardingTargetForSelector
是否存在。如果存在则继续向下执行过程2。如果不存在,跳到loc_115bab
。
过程2: 执行快速转发forwardingTargetForSelector
。如果返回为nil
或者返回对象为self
, 则也跳到loc_115bab
去执行。 否则将继续向下执行,直到loc_115ef1
,结束执行。
class_respondsToSelector:
/***********************************************************************
* class_respondsToSelector.
**********************************************************************/
BOOL class_respondsToMethod(Class cls, SEL sel)
{
OBJC_WARN_DEPRECATED;
return class_respondsToSelector(cls, sel);
}
BOOL class_respondsToSelector(Class cls, SEL sel)
{
return class_respondsToSelector_inst(nil, sel, cls);
}
// inst is an instance of cls or a subclass thereof, or nil if none is known.
// Non-nil inst is faster in some cases. See lookUpImpOrForward() for details.
NEVER_INLINE __attribute__((flatten)) BOOL
class_respondsToSelector_inst(id inst, SEL sel, Class cls)
{
// Avoids +initialize because it historically did so.
// We're not returning a callable IMP anyway.
return sel && cls && lookUpImpOrNilTryCache(inst, sel, cls, LOOKUP_RESOLVER);
}
class_respondsToSelector
在objc-class.mm
中实现,用来查找执行类中cls
中是否存在指定方法sel
,最终还是会调用_lookUpImpTryCache
。_lookUpImpTryCache
,先执行快速查找,找不到后会执行慢速查找。
3.2 methodSignatureForSelector:
loc_141957
:
执行loc_141957
,进行僵尸对象判断后,会继续向下执行。类似的,也是先检查methodSignatureForSelector:
是否存在,再继续执行methodSignatureForSelector:
。
当methodSignatureForSelector:
不存在时,或者methodSignatureForSelector:
返回结果为空时,最终会执行到loc_115fc1:
,doesNotRecognizeSelector:
。
当methodSignatureForSelector:
存在,并且methodSignatureForSelector:
返回结果不为空时,会继续向下执行,loc_115c0e:
。
Core Foundation
为methodSignatureForSelector
提供了默认实现,双击methodSignatureForSelector:
,出现弹窗
选择第1项,进入实例方法methodSignatureForSelector
的默认实现。
/* @class NSObject */
-(void *)methodSignatureForSelector:(void *)arg2 {
rdx = arg2;
rdi = self;
if ((rdx != 0x0) && (___methodDescriptionForSelector(object_getClass(rdi), rdx) != 0x0)) {
rax = [NSMethodSignature signatureWithObjCTypes:rdx];
}
else {
rax = 0x0;
}
return rax;
}
这里会进行一个if
判断, 如果arg2
不为空,并且___methodDescriptionForSelector
执行结果为空,就返回一个NSMethodSignature
对象,否则就返回空。
objc_opt_class
Class
objc_opt_class(id obj)
{
#if __OBJC2__
if (slowpath(!obj)) return nil;
Class cls = obj->getIsa();
if (fastpath(!cls->hasCustomCore())) {
return cls->isMetaClass() ? obj : cls;
}
#endif
return ((Class(*)(id, SEL))objc_msgSend)(obj, @selector(class));
}
当obj
为空时,返回nil
。否则返回obj
的类Class
。
___methodDescriptionForSelector
int ___methodDescriptionForSelector(int arg0, int arg1) {
rbx = arg1;
var_40 = arg0;
if (arg0 == 0x0) goto loc_120833;
loc_120755:
r12 = var_40;
var_50 = rbx;
goto loc_12075d;
loc_12075d:
var_34 = 0x0;
rax = class_copyProtocolList(r12, &var_34);
if (var_34 == 0x0) goto loc_1207ea;
loc_120776:
r13 = 0x0;
var_48 = rax;
goto loc_12077d;
loc_12077d:
r15 = *(rax + r13 * 0x8);
rdi = r12;
r14 = r12;
r12 = rax;
rax = class_isMetaClass(rdi);
rax = protocol_getMethodDescription(r15, rbx, 0x1, (rax ^ 0x1) & 0xff);
if (rax != 0x0) goto loc_1207f6;
loc_1207a9:
r15 = *(r12 + r13 * 0x8);
rax = class_isMetaClass(r14);
rax = protocol_getMethodDescription(r15, rbx, 0x0, (rax ^ 0x1) & 0xff);
r12 = r14;
if (rax != 0x0) goto loc_120804;
loc_1207cf:
r13 = r13 + 0x1;
rbx = var_50;
rax = var_48;
if (r13 < var_34) goto loc_12077d;
loc_1207e2:
rbx = 0x0;
r15 = 0x0;
goto loc_12080e;
loc_12080e:
free(rax);
if (r15 != 0x0) goto loc_12085a;
loc_12081b:
rax = class_getSuperclass(r12);
r12 = rax;
rbx = var_50;
if (rax != 0x0) goto loc_12075d;
loc_120833:
rax = class_getInstanceMethod(var_40, rbx);
if (rax != 0x0) {
rax = method_getDescription(rax);
r15 = *rax;
rbx = *(rax + 0x8);
}
else {
rbx = 0x0;
r15 = 0x0;
}
goto loc_12085a;
loc_12085a:
if (**___stack_chk_guard == **___stack_chk_guard) {
rax = r15;
}
else {
rax = __stack_chk_fail();
}
return rax;
loc_120804:
r15 = rax;
rbx = 0x0;
rax = var_48;
goto loc_12080e;
loc_1207f6:
r15 = rax;
rbx = 0x1;
rax = r12;
r12 = r14;
goto loc_12080e;
loc_1207ea:
if (rax == 0x0) goto loc_12081b;
loc_1207ef:
r15 = 0x0;
rbx = 0x0;
goto loc_12080e;
}
___methodDescriptionForSelector:
涉及循环查找, goto
比较多,可以参照如下流程图理解
- 用
class_copyProtocolList
、protocol_getMethodDescription
方法,检查是否有对应的selector
,不管是否实现 - 用
class_getInstanceMethod
检查selector
是否有实现
问题一中,当没有在方法决议中处理
sayHello
方法,也没有实现快速转发方法,系统调用会来到methodSignatureForSelector:
的默认实现,从而执行___methodDescriptionForSelector
方法,会调用class_getInstanceMethod
,最终会返回空。前面我们提到
class_getInstanceMethod
其实也会最终调用lookUpImpOrForward
查找方法,所以会在第二次慢速查找过程中,再次来到resolveInstanceMethod:
。所以在方法实现找不到时,如果默认不做处理,会执行两次方法决议方法。
3.3 _forwardStackInvocation:
loc_115c0e:
查找_forwardStackInvocation:
,这是一个私有方法,如果找不到,回调到loc_115d61
执行forwardInvocation:
。
所以问题二中,会在在
methodSignatureForSelector:
之后,forwardInvocation:
之前,有一次_forwardStackInvocation:
的方法决议。
3.4 forwardInvocation:
loc_115d61:
通过伪码分析,只要执行了forwardInvocation:
,不管有没有进行转发,都不会调到doesNotRecognizeSelector:
了。但情况并非如此,我们将上面的转发代码注释掉,运行仍会发生崩溃,原因是我们调用了父类NSObject
的forwardInvocation:
。因此,如果想不崩溃,就不要调用[super forwardInvocation:anInvocation];
。
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"慢速转发 forwardInvocation: %@", anInvocation);
// if (anInvocation.selector == @selector(sayHello)) {
// [anInvocation invokeWithTarget:[ZStudent alloc]];
// } else {
[super forwardInvocation:anInvocation];
// }
}
3.5 doesNotRecognizeSelector:
loc_115fc1:
双击,查看doesNotRecognizeSelector:
的默认实现:
/* @class NSObject */
-(void)doesNotRecognizeSelector:(void *)arg2 {
_CFLog(0x3, @"%@: unrecognized selector sent to instance %p", ___CFExceptionProem(self, arg2), self, r8, r9, stack[-40]);
objc_exception_throw([NSException exceptionWithName:*_NSInvalidArgumentException reason:__CFAutoreleasePoolAddObject(*_kCFAllocatorSystemDefault, _CFStringCreateWithFormat(*_kCFAllocatorSystemDefault, 0x0, @"%@: unrecognized selector sent to instance %p"), @"%@: unrecognized selector sent to instance %p", rax, self) userInfo:0x0]);
return;
}
输出错误信息,并抛出异常。
当未实现的消息,最终都没有被决议或转发处理时,会来到doesNotRecognizeSelector:
。可以自定义实现,来避免最后的崩溃,也可以进行崩溃堆栈的记录上传。
4.总结
当试图调用没有没有实现的方法时,会进入以下流程:
resolveInstanceMethod
:方法决议,为发送消息的对象的添加一个动态添加一个IMP
,然后再执行forwardingTargetForSelector
:快速转发,将该消息直接转发给一个能处理该消息的对象methodSignatureForSelector
和forwardInvocation
:第一个方法生成方法签名,然后创建NSInvocation
对象作为参数给第二个方法,然后在第二个方法里面做消息处理,只要在第二个方法里面不执行父类的方法,即使不处理也不会崩溃- 以上三步,在任何一步正确处理,都不会产生崩溃,否则将报错崩溃