动态方法决议 与 消息转发

936 阅读18分钟

前言

前文介绍的方法的查找流程:快速查找和慢速慢速查找流程。但是并没有讲当方法实现没有找到时,系统是如何处理的,本文将介绍这一部分。

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类,声明sayHellosayNB方法,但是只实现了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的实现impsayHellosel绑定到一起。

进入实例方法动态决议: 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 -l400 发现只在NSObject中提供了定义和默认实现,没有找到更多信息。 但是没事,既然是OC方法,我们先去官方文档找一找。

-l600 通过文档,大概知道此方法可返回一个对象,此对象用来接收处理传入的方法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

-l600 当一个对象收到一条没有相应方法实现的消息时,运行时系统会给接收者一个机会——将消息委托给另一个接收者处理。它通过创建一个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: -l600

除了实现forwardInvocation:方法之外,还需要重写methodSignatureForSelector:方法。系统要想获得创建NSInvocation对象需要的信息,需要methodSignatureForSelector:方法为给定的方法选择器SEL提供一个方法签名。 methodSignatureForSelector:也可以返回nil,这样就不会执行forwardInvocation:了。

注意2: -l600 NSObject提供的默认实现只是调用方法;它不转发任何消息。因此,如果您选择不实现forwardInvocation:,向对象发送无法识别的消息将引发异常,从而会执行doesNotRecognizeSelector:方法。

2.4 methodSignatureForSelector

-l600

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. 转发流程的汇编分析

  1. 找到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

  2. 打开hoper, 将CoreFoundation的可执行文件拖入。

  3. 由于是在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,结束执行。 -w500

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_respondsToSelectorobjc-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 FoundationmethodSignatureForSelector提供了默认实现,双击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比较多,可以参照如下流程图理解 -w600

  1. class_copyProtocolListprotocol_getMethodDescription方法,检查是否有对应的selector,不管是否实现
  2. 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:了。但情况并非如此,我们将上面的转发代码注释掉,运行仍会发生崩溃,原因是我们调用了父类NSObjectforwardInvocation:。因此,如果想不崩溃,就不要调用[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.总结

当试图调用没有没有实现的方法时,会进入以下流程:

  1. resolveInstanceMethod:方法决议,为发送消息的对象的添加一个动态添加一个IMP,然后再执行
  2. forwardingTargetForSelector:快速转发,将该消息直接转发给一个能处理该消息的对象
  3. methodSignatureForSelectorforwardInvocation:第一个方法生成方法签名,然后创建NSInvocation对象作为参数给第二个方法,然后在第二个方法里面做消息处理,只要在第二个方法里面不执行父类的方法,即使不处理也不会崩溃
  4. 以上三步,在任何一步正确处理,都不会产生崩溃,否则将报错崩溃