objc_msgSend 流程分析(消息转发)

前言

在前面的 objc_msgSend 流程分析(快速查找) 和 objc_msgSend 流程分析(慢速查找) 我们分析了 objc_msgSend 的快速查找以及慢速查找流程,如果这两步都没有找到,会怎么样呢?

首先,我们来看个示例,定义一个 LCPerson 类,声明一个实例方法和一个类方法,实例方法和类方法均没有实现

@interface LCPerson : NSObject

- (void)say666;

+ (void)say888;
    
@end

@implementation LCPerson

@end

main 函数中分别调用实例方法和类方法

  • 调用实例方法 say666
  • 调用类方法 say888

可以看到,运行程序,都崩溃了,报错也是我们在开发中最常见的方法未实现。

方法未实现报错源码

根据上一篇消息的慢速查找流程,我们发现,方法查找不到,最终会走到 __objc_msgForward_impcache 方法,查看它的汇编实现

STATIC_ENTRY __objc_msgForward_impcache

// No stret specialization.
b    __objc_msgForward

END_ENTRY __objc_msgForward_impcache


ENTRY __objc_msgForward

adrp    x17, __objc_forward_handler@PAGE
ldr    p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17

END_ENTRY __objc_msgForward

以上主要调用了 __objc_forward_handler,继续查看 __objc_forward_handler 的实现,并没有找到, 在源码中去掉一个下划线进行全局搜索 _objc_forward_handler,有如下实现,本质是调用的objc_defaultForwardHandler 方法

// Default forward handler halts the process.
__attribute__((noreturn, cold)) 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;

看到这里,是不是很熟悉,这就是我们在开发中常见的错误:方法没实现

objc_msgSend 是不是走完了快速查找和慢速查找后就直接崩溃了呢?其实并不是,苹果还给了最后的三次机会,动态方法决议快速转发慢速转发

动态方法决议

慢速查找 流程未找到方法实现时,首先会尝试一次 动态方法决议,其源码如下:

static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());

    runtimeLock.unlock();

    //如果当前的 Class 不是元类,则调用对象的解析方法
    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(inst, sel, cls);
    }
    //如果当前的 Class 是元类,则调用类的解析方法
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(inst, sel, cls);
        //类方法在元类中是对象方法,所以需要查询元类中对象方法的动态方法决议
        if (!lookUpImpOrNil(inst, sel, cls)) {
            //如果没有找到或者为空,在元类的对象方法解析方法中查找
            resolveInstanceMethod(inst, sel, cls);
        }
    }

    // chances are that calling the resolver have populated the cache
    // so attempt using it
    //如果方法解析中将其实现指向其他方法,则继续走方法查找流程
    return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}

流程图如下

实例方法 resolveInstanceMethod

查看它的源码实现

static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);

    if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
        // Resolver not implemented.
        return;
    }

    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
    IMP imp = lookUpImpOrNil(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));
        }
    }
}
  • 查找 cls 类中是否有该方法的实现,通过 lookUpImpOrNil 方法又会进入慢速查找流程查找 resolveInstanceMethod 方法
    • 没有,直接返回
    • 有,发送 resolveInstanceMethod 消息
  • 通过 lookUpImpOrNil 方法再次进入慢速查找流程查找 实例方法

类方法 resolveClassMethod

源码实现如下

static void resolveClassMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    ASSERT(cls->isMetaClass());

    if (!lookUpImpOrNil(inst, @selector(resolveClassMethod:), cls)) {
        // Resolver not implemented.
        return;
    }

    Class nonmeta;
    {
        mutex_locker_t lock(runtimeLock);
        nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
        // +initialize path should have realized nonmeta already
        if (!nonmeta->isRealized()) {
            _objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
                        nonmeta->nameForLogging(), nonmeta);
        }
    }
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(nonmeta, @selector(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(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 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));
        }
    }
}

可以看到,实现与实例方法类似,只是传入的 cls 不再是 ,而是 元类

示例验证

我们在 LCPerson 类中实现 resolveInstanceMethod 方法,并打印信息验证是否会走这个方法

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    
    if (sel == @selector(say666)) {
        NSLog(@"%s 来了", __func__);
    }
    
    return [super resolveInstanceMethod:sel];
}

运行项目,查看结果

从上面的打印可以发现,在崩溃之前确实调用了 resolveInstanceMethod 方法,但是为什么会调用两次呢?实际上第一次的打印是在查找 say666 方法时会进入 动态方法决议,第二次打印是在慢速转发流程中调用了 CoreFoundation 框架中的 methodSignatureForSelector 后,会再次进入动态决议,这个在文末再详解。

崩溃解决

知道了动态方法决议,我们就可以通过在类中重写 resolveInstanceMethod 类方法,并将其指向其他方法的实现,即在 LGPerson 中重写resolveInstanceMethod 类方法,将实例方法 say666 的实现指向 sayeMethod 方法实现,如下所示

- (void)sayeMethod {
    NSLog(@"%s", __func__);
}


+ (BOOL)resolveInstanceMethod:(SEL)sel {

    if (sel == @selector(say666)) {
        NSLog(@"%s 来了", __func__);

        IMP imp = class_getMethodImplementation(self, @selector(sayeMethod));
        Method method = class_getInstanceMethod(self, @selector(sayeMethod));
        const char *type = method_getTypeEncoding(method);
        return class_addMethod(self, sel, imp, type);
    }

    return [super resolveInstanceMethod:sel];
}

重新运行,其打印结果如下

消息转发

包括快速转发和慢速转发

快速转发

当慢速查找,以及动态方法决议均没有找到实现时,会走消息转发,首先是进行快速消息转发,即 forwardingTargetForSelector

- 如果返回消息接收者,在消息接收者中查找
- 如果返回nil,则进入下一步 `慢速转发`
验证

针对前面提到的崩溃,如果动态方法决议也没有找到实现,我们使用另外一中方法解决。我们创建一个 LCPeople 类,声明并实现 say666 实例方法

@interface LCPeople : NSObject

- (void)say666;

@end

@implementation LCPeople

- (void)say666 {
    NSLog(@"%s", __func__);
}

@end

LCPerson 类中重写 forwardingTargetForSelector 方法,将 LCPerson 的实例方法的消息接收者指定为 LCPeople 的对象,如下:

- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
    return [LCPeople alloc];
}

再次运行,执行结果如下

慢速转发

如果在上一步(快速转发)中还是没有找到,还有最后的一次挽救机会,即在 LCPerson 中重写 methodSignatureForSelector,如下所示

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSLog(@"%s", __func__);
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"%s - %@",__func__,anInvocation);
}

运行代码,执行结果如下

从打印结果发现 forwardInvocation 方法中不对 invocation 进行处理,也不会崩溃报错。

探索

在前面提到重写 resolveInstanceMethod: 方法时,会执行两次,这是为什么呢?首先我们去源码调用的地方,在 resolveInstanceMethod 源码实现中看到触发 resolveInstanceMethod: 方法的代码

我们打个断点,看下调用堆栈,看下到底发生了什么,运行代码,第一次打印 来了 时,bt 打印堆栈信息

执行下一步,知道打印第二次来了,bt 打印堆栈信息

以上我们可以看到,第一次打印时,selsay666,第二次打印时,可以看到是通过 CoreFoundation 的-[NSObject(NSObject) methodSignatureForSelector:] 方法,然后通过 class_getInstanceMethod 再次进入动态方法决议。

消息转发流程图

总结

结合前面2篇的解析,objc_msgSend 消息发送的流程就分析完成了,总的概括下

  • 快速查找:在 类的缓存 中查找方法的实现

  • 慢速查找:缓存中没有找到方法的实现,则去 类的方法列表 中查找,如果没有找到则去 继承链的缓存和方法列表 中查找

  • 动态方法决议:如果慢速查找还是没有找到时,尝试一次 动态方法决议,即重写 resolveInstanceMethod / resolveClassMethod 方法

  • 消息转发:如果动态方法决议还是实现,则进行消息转发:快速转发+慢速转发

  • 消息转发没有实现,则程序崩溃:unrecognized selector sent to instance xxx

你可能感兴趣的:(objc_msgSend 流程分析(消息转发))