Runtime底层原理--动态方法解析总结


动态方法解析总结

方法的底层会编译成消息,消息进行递归,先从实例方法开始查找,到父类最后到NSObject。如果在汇编部分快速查找没有找到IMP,就会进入C/C++中的动态方法解析进入lookUpImpOrForward方法进行递归。

动态方法解析

动态方法解析分为实例方法和类方法两种。

实例方法查找imp流程和动态方法解析

比如执行一个Student实例方法eat,会先去这个类中查找是否有该方法(sel),如果有则进行存储以便下次直接从汇编部分快速查找。

// Try this class's cache.
    // Student元类 - 父类 (根元类) -- NSObject
    // resovleInstance 防止递归 --
    imp = cache_getImp(cls, sel);
    if (imp) goto done;

    // Try this class's method lists.
    {
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }

如果没有sel那么接下来去父类(直到NSObject)的缓存和方法列表找查找。如果在父类中找到先缓存再执行done.
// 元类的父类 - NSObject 是否有 实例方法
        for (Class curClass = cls->superclass;
             curClass != nil;
             curClass = curClass->superclass)
        {
            // Halt if there is a cycle in the superclass chain.
            if (--attempts == 0) {
                _objc_fatal("Memory corruption in class list.");
            }
            
            // Superclass cache.
            imp = cache_getImp(curClass, sel);
            if (imp) {
                if (imp != (IMP)_objc_msgForward_impcache) {
                    // Found the method in a superclass. Cache it in this class.
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }

如果最终还是没找到,则会进入动态方法解析_class_resolveMethod,先判断当前cls对象是不是元类,也就是如果是对象方法会走到_class_resolveInstanceMethod方法,
/***********************************************************************
* _class_resolveMethod
* Call +resolveClassMethod or +resolveInstanceMethod.
* Returns nothing; any result would be potentially out-of-date already.
* Does not check if the method already exists.
**********************************************************************/
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]

        _class_resolveInstanceMethod(cls, sel, inst);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        _class_resolveClassMethod(cls, sel, inst);
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            _class_resolveInstanceMethod(cls, sel, inst);
        }
    }
}

如果元类,那么执行_class_resolveInstanceMethod(cls, sel, inst)方法,该方法会执行lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, NO/*initialize*/, YES/*cache*/, NO/*resolver*/),查找当前的cls的isa是否实现了resolveInstanceMethod,也就是是否有自定义实现、是否重写了。如果查到了就会给类对象发送消息objc_msgSend,调起resolveInstanceMethod方法
/***********************************************************************
* lookUpImpOrNil.
* Like lookUpImpOrForward, but returns nil instead of _objc_msgForward_impcache
**********************************************************************/
IMP lookUpImpOrNil(Class cls, SEL sel, id inst, 
                   bool initialize, bool cache, bool resolver)
{
    IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
    if (imp == _objc_msgForward_impcache) return nil;
    else return imp;
}

lookUpImpOrNil的内部是通过lookUpImpOrForward方法进行查找,再次回到递归调用。

如果还是没查到,这里就不会再次进入动态方法解析(注:如果再次进入动态方法解析会形成死递归),首先对cls的元类进行查找,然后元类的父类,也就是根元类(系统默认实现的虚拟的)进行查找、最终到NSObjece,只不过NSObjece中默认实现resolveInstanceMethod方法返回NO,也就是此时在元类进行查找的时候找到了resolveInstanceMethod方法,并停止继续查找,这就是为什么动态方法解析后的递归没有再次进入动态方法解析的原因。如果最终还是没有找到SEL_resolveInstanceMethod则说明程序有问题,直接返回。下面是isa走位图:
isa走位流程

如果找到的imp不是转发的imp,则返回imp。
举个例子:
在Student中有个对象run方法,但是并没有实现,当调用run方法时,最终没有找到imp会崩溃。通过动态方法解析,实现run方法

#pragma mark - 动态方法解析
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    
    NSLog(@"动态方法解析 - %@",self);
    if (sel == @selector(run)) {
        // 我们动态解析对象方法
        NSLog(@"对象方法 run 解析走这里");
        SEL readSEL = @selector(readBook);
        Method readM= class_getInstanceMethod(self, readSEL);
        IMP readImp = method_getImplementation(readM);
        const char *type = method_getTypeEncoding(readM);
        return class_addMethod(self, sel, readImp, type);
    }
    return [super resolveInstanceMethod:sel];
}

此时只是给对象方法添加了一个imp,接下来再次进入查找imp流程,重复之前的操作,只不过现在对象方法已经有了imp。
/***********************************************************************
* _class_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 _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
    if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, SEL_resolveInstanceMethod, 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(cls, sel, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
// ...省略N行代码

动态方法解析的实质: 经过漫长的查找并没有找到sel的imp,系统会发送resolveInstanceMethod消息,为了防止系统崩溃,可以在该方法内对sel添加imp,系统会自动再次查找imp。

类方法查找imp流程和动态方法解析

类方法查找imp流程和实例方法查找imp前面流程一样,也是从汇编部分快速查找,之后判断cls是不是元类,在元类方法列表中查找,如果元类中没有当前的sel,就去元类的父类中查找,还没有就去根元类的父类NSObject中查找,此时查找的就是NSObject中是否有这个实例对象方法,如果NSObject中也没有就会进入动态方法解析_class_resolveMethod。类对象这里的cls和对象方法不一样,因为cls是元类所以直接走_class_resolveClassMethod方法。进入_class_resolveClassMethod方法还是先判断resolveClassMethod方法是否有实现,之后发送消息objc_msgSend,这里和实例方法有所区别,类方法会执行_class_getNonMetaClass方法,内部实现getNonMetaClassgetNonMetaClass会判断当前cls是不是NSObject,判断当前的cls是不是根元类,也就是自己,接下来判断inst类对象,判断inst类对象的isa如果不是元类,那么返回类对象的父类,不是就返回类对象。在_class_resolveClassMethod方法中添加了imp后还是和实例方法一样,再次进入重新查找流程,此时如果还是没有,那么类方法还会再一次的进入_class_resolveInstanceMethod方法,和实例方法不同的是resolveInstanceMethod方法内部的cls是元类,所以找的方法也就是- (BOOL)resolveClassMethod:(SEL)sel,可以在NSObject中添加+ (BOOL)resolveClassMethod:(SEL)sel方法,这样无论类方法还是实例方法都会走到这里,可以作为防崩溃的处理

/***********************************************************************
* getNonMetaClass
* Return the ordinary class for this class or metaclass. 
* `inst` is an instance of `cls` or a subclass thereof, or nil. 
* Non-nil inst is faster.
* Used by +initialize. 
* Locking: runtimeLock must be read- or write-locked by the caller
**********************************************************************/
static Class getNonMetaClass(Class metacls, id inst)
{
    static int total, named, secondary, sharedcache;
    runtimeLock.assertLocked();

    realizeClass(metacls);

    total++;

    // return cls itself if it's already a non-meta class
    if (!metacls->isMetaClass()) return metacls;

    // metacls really is a metaclass

    // special case for root metaclass
    // where inst == inst->ISA() == metacls is possible
    if (metacls->ISA() == metacls) {
        Class cls = metacls->superclass;
        assert(cls->isRealized());
        assert(!cls->isMetaClass());
        assert(cls->ISA() == metacls);
        if (cls->ISA() == metacls) return cls;
    }

    // use inst if available
    if (inst) {
        Class cls = (Class)inst;
        realizeClass(cls);
        // cls may be a subclass - find the real class for metacls
        while (cls  &&  cls->ISA() != metacls) {
            cls = cls->superclass;
            realizeClass(cls);
        }
        if (cls) {
            assert(!cls->isMetaClass());
            assert(cls->ISA() == metacls);
            return cls;
        }

我们在Student类中添加未实现的类方法walk,在NSObject类中添加一个对象方法walk,运行程序不会崩溃。类方法先递归,开始找父类,最终在NSObject类中好到对象方法walk

TIP:对象方法存储在类中,类方法存储在元类里面,类对象以实例方法的形式存储在元类中。可以通过输出class_getInstanceMethod方法和class_getClassMethod方法的imp指针来验证,当然源码也可以解释在cls的元类中查找实例方法

/***********************************************************************
* 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);
}

还可以通过LLDB进行验证,动态方法解析的时候执行lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver)方法,这里的cls就是inst的元类

#   define ISA_MASK        0x00007ffffffffff8ULL

// -------------------------------------------------
#if SUPPORT_NONPOINTER_ISA

inline Class 
objc_object::ISA() 
{
    assert(!isTaggedPointer()); 
#if SUPPORT_INDEXED_ISA
    if (isa.nonpointer) {
        uintptr_t slot = isa.indexcls;
        return classForIndex((unsigned)slot);
    }
    return (Class)isa.bits;
#else
    return (Class)(isa.bits & ISA_MASK);
#endif
}

这里看到初始化的时候isa.bits & ISA_MASK,我们先后打印cls和inst的信息,也可以验证当前指针指向当前的元类。
LLDB信息

动态方法解析作用

适用于重定向,也可以做防崩溃处理,也可以做一些错误日志收集等等。动态方法解析本质就是提供机会(任何没有实现的方法都可以重新实现)。

该文章为记录本人的学习路程,希望能够帮助大家!!!


文章作者: Vincent
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Vincent !
  目录