OC底层原理-动态方法决议

当lookupImpOrForward函数从cache和methodTable中找不到对应Method,继续向下执行就会来到resolveMethod_locked函数也就是我们常说的动态方法决议

    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

resolveMethod_locked

    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 (!lookUpImpOrNilTryCache(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }

    // chances are that calling the resolver have populated the cache
    // so attempt using it
    return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);

只执行一次

behavior & LOOKUP_RESOLVER
behavior ^= LOOKUP_RESOLVER;
这俩步操作保证resolveMethod_locked只被执行一次

resolveInstanceMethod

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

    if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
        // Resolver not implemented.
       //根类NSObject有默认实现兜底,不会走到这里
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
   //向cls对象发送resolveInstanceMethod:消息,参数为当前的sel
    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 = 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));
        }
    }
}

resolveClassMethod

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

    if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
        // Resolver not implemented.
       // NSObject有兜底实现
        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 = 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 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));
        }
    }
}

resolveMethod_locked会发送resolveInstanceMethod:和resolveClassMethod:消息,为了减少程序的崩溃提用户体验,苹果在这里给开发者一次机会去补救,这个过程就叫做动态方法决议。这里也体现了aop编程思想,在objc_msg流程中给开发者提供了一个切面,切入自己想要处理,比如安全处理,日志收集等等。

resolveClassMethod

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

    if (!lookUpImpOrNilTryCache(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 = 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 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));
        }
    }
}

看完源码思考俩个问题

1.为什么在resloveInstanceMethod函数中调用了一次lookUpImpOrNilTryCache,resolveMethod_locked函数最后又调用了一次lookUpImpOrNilTryCache?这俩次分别有什么作用?

  • 第一次TryCache流程分析

堆栈信息-->第一次tryCache会把我动态添加的方法存进cache
截屏2021-07-01 下午4.21.41.png

本次TryCache,会调用lookUpImpOrForWard函数查找MethodTable。入参behavior值为4,找不到imp的话不会再走动态决议和消息转发,直接return nil,分支如下:

    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }

所以这次tryCache实际的作用就是在动态决议添加方法之后,找到方法,并调用log_and_fill_cache函数存进缓存(佐证了下面这段注释)

   // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveInstanceMethod adds to self a.k.a. cls
  • 第二次TryCache流程分析

这次我们直接看注释吧

    // chances are that calling the resolver have populated the cache
    // so attempt using it

调用动态决议可能填充了得缓存,并尝试使用它。嗯,第二次tryCache的作用已经简单明了。
本次调用入参behavior值为1,methodTable查找不到imp不会走动态决议流程,但会调用消息转发

  • 为什么分为俩次呢,一次不行吗?

为什么不最后查找方法,填充缓存再返回,反而要先填充缓存,再尝试从缓存中查找,这么做有什么好处呢?

有个关于多线程的猜想:

假如线程a发送消息s进入了动态决议流程,此时线程b也发送消息s,这时候如果缓存中有已添加的imp响应消息s,是不是就不会继续慢速查找,动态决议等后续流程。这么想,动态决议添加的方法是不是越先添加到缓存越好。

另外一点我们看到resolveClassMethod之后,也尝试从缓存中查找,而且找不到又调用了一遍resolveInstanceMethod。

可已看出苹果开发者在设计这段流程的思考可能是:
既然你愿意通过动态方法决议去添加这个imp,费了这么大功夫,很显然你想使用该imp,而且使用的频率可能不低。既然如此在resolver方法调用完毕,我就帮你放进缓存吧。以后你想用直接从缓存中找。

2. 为什么类resolver之后会尝试调用instance的resolver?难道instance的resolver还能解决类方法缺失的问题?

关于这个问题,我们来看张经典的
isa流程图.png

如果我们查找一个类方法沿着继承链最终会找到NSObject(rootMetaClass的父类是NSObject),这会导致一个有意思的问题:我们的NSObject对象方法可以响应类方法的sel

看个实例

给NSObect添加个instaceMethod


截屏2021-07-02 上午9.53.58.png

发送个类方法消息
截屏2021-07-02 上午9.55.53.png

是不是很惊喜,其实我们底层对classMethod和InstanceMethod根本没有区分,classMethod也是InstanceMethod

* class_getClassMethod.  Return the class method for the specified
* class and selector.
**********************************************************************/
Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    return class_getInstanceMethod(cls->getMeta(), sel);
}

只不过,找classMethod是从MetaClass查找InstanceMethod,找InstanceMethod是从class找InstanceMethod。
透过现象看本质,这里就可以解释,为什么resolveClass完毕,缓存中找不到imp,会再次调用resolveInstance。显然,我们给NSObject添加InstanceMethod可以解决问题,而且可以在这里我们也可以添加classMethod。毕竟classMethod也是InstanceMethod。

你可能感兴趣的:(OC底层原理-动态方法决议)