iOS-OC底层原理_动态方法决议及消息转发

前言

在之前的的objc_msgSend()探索中,当调用一个方法后,首先会进入快速查找来匹配sel对应的imp;当没有找到时,会进入慢速查找,开始匹配;当依然无法匹配时,苹果给出了一次转发的机会(动态方法决议):

// No implementation found. Try method resolver once.
if (slowpath(behavior & LOOKUP_RESOLVER)) {
    behavior ^= LOOKUP_RESOLVER;
    return resolveMethod_locked(inst, sel, cls, behavior);
}

从注释:// No implementation found. Try method resolver once.也是一目了然。
这篇文章,我们就一起探索下resolveMethod_locked()的内部实现。

开始

直接看下resolveMethod_locked()的源码:

static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());
    // 方法没有你怎么不知道
    // 报错
    // 给你一次机会
    runtimeLock.unlock();

    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(inst, sel, cls);
    } 
    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);
}

首先判断当前的cls是否为元类,当不是元类则调用resolveInstanceMethod(),否则调用resolveClassMethod()。接下来分别看下两个方法的具体实现:
static void resolveInstanceMethod(id inst, SEL sel, Class cls)

/***********************************************************************
* resolveInstanceMethod
* Call +resolveInstanceMethod, looking for a method to be added to class cls.
* cls may be a metaclass or a non-meta class.
* Does not check if the method already exists.
**********************************************************************/
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));
        }
    }
}

该方法一开始的注释写的就很清楚:

  • Call +resolveInstanceMethod, looking for a method to be added to class cls.

意味着,resolveInstanceMethod执行会响应+resolveInstanceMethod,我们可以通过在类中实现该方法,来接收响应,并做处理。同时我们可以看到发起响应的关键代码:

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);

即通过objc_msgSendcls发送了一个resolveInstanceMethod消息。
static void resolveClassMethod(id inst, SEL sel, Class cls)

//在cls中添加并实现
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    //自定义动态处理
   return [super resolveInstanceMethod:sel];
}

再来看下 resolveClassMethod的具体实现

/***********************************************************************
* resolveClassMethod
* Call +resolveClassMethod, looking for a method to be added to class cls.
* cls should be a metaclass.
* Does not check if the method already exists.
**********************************************************************/
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中添加并实现该方法来接收响应,关键的触发响应代码,同样是通过objc_msgSend发送消息,跟resolveInstanceMethod的逻辑如出一辙:

BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);

回到我们的类:

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
  //添加自定义处理
   return [super resolveInstanceMethod:sel];
}
+ (BOOL)resolveClassMethod:(SEL)sel
{
  //添加自定义处理
   return [super resolveClassMethod:sel];
}

消息转发

当方法决议后依然没有匹配到imp时,会触发消息转发机制,由libObjC转向CoreFoundation处理并触发转发回调,这个可以在Crash时的堆栈信息中看到:

截屏2020-10-15 下午2.54.31.png

关于CoreFoundation的源码这里就不展开验证了(需要借助一些反编译工具查看一些伪代码)。
此时会触发以下几个熟悉的回调函数:

//快速转发流程
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    return nil;
}
//慢速转发流程
//1.方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
//2.转发处理
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
//    [anInvocation invoke];
}
//当没有实现任何处理时
- (void)doesNotRecognizeSelector:(SEL)aSelector
{
    
}

在日常的开发中,我们可以在适当的位置,做响应的容错处理代码。

消息转发简单流程图

消息转发流程.008.jpeg

这里解释下resolveClassMethod->resolveInstanceMethod:
当调用类方法的时候,Objc会去该类元类中去查找对应的IMP,没有找到会去他的父元类中查找,最终会查询到根元类根元类依然没有找到,会继续去根元类的父类,即根类(NSObject)中进行查找,而类中只有实例方法,此时objc内部做了一些处理,当在根元类无法找到IMP时,直接响应父类(NSObject类)resolveInstanceMethod方法,可以在如下的源码中体现:

static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());
    runtimeLock.unlock();
    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(inst, sel, cls);
    } 
    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);
}
总结

至此,我们能看到,在我们程序将要发生崩溃时,我们有两次避免崩溃的机会,即动态方法决议消息转发处理,前者是在libObjC下触发,或者在CoreFoundation下触发。在实际开发中我们应该尽量避免在方法决议层做逻辑处理,而是在更底层的消息转发层做相应的容错处理。

你可能感兴趣的:(iOS-OC底层原理_动态方法决议及消息转发)