RunTime源码阅读(十)之方法交换原理

2020-01-17  本文已影响0人  某非著名程序员

1.RunTime方法交换

//self是类对象,testExchangeInstanceMethod是实例方法,查询实例方法需要在类方法中查询,即可直接在类对象中查询
    Method instanceMethod = class_getInstanceMethod(self, @selector(testExchangeInstanceMethod));
    Method instanceMethodAfter = class_getInstanceMethod(self, @selector(testExchangeInstanceMethodAfter));
    method_exchangeImplementations(instanceMethod, instanceMethodAfter);
    
    //self是类对象,testExchangeClassMethod是类方法,查询类方法需要在元类中查询,该方法多了层cls->getMeta()
    Method classMethod = class_getClassMethod(self, @selector(testExchangeClassMethod));
    Method classMethodAfter = class_getClassMethod(self, @selector(testExchangeClassMethodAfter));
    method_exchangeImplementations(classMethod, classMethodAfter);

2.原理

2.1 class_getInstanceMethod

Method class_getInstanceMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;
    // Search method lists, try method resolver, etc.(搜索方法列表,尝试方法解析器)
    lookUpImpOrNil(cls, sel, nil, 
                   NO/*initialize*/, NO/*cache*/, YES/*resolver*/);
    return _class_getMethod(cls, sel);
}

cls与sel都不能为空
lookUpImpOrNil 查找sel方法,并填充到缓存中

_class_getMethod源码

static Method _class_getMethod(Class cls, SEL sel)
{
    mutex_locker_t lock(runtimeLock);
    return getMethod_nolock(cls, sel);
}

static method_t *
getMethod_nolock(Class cls, SEL sel)
{
    method_t *m = nil;
    runtimeLock.assertLocked();
    // fixme nil cls?
    // fixme nil sel?
    assert(cls->isRealized());
    while (cls  &&  ((m = getMethodNoSuper_nolock(cls, sel))) == nil) {
        cls = cls->superclass;
    }
    return m;
}

getMethodNoSuper_nolock查找的方法,如果当前cls没找到,沿着superclass指针查找。

getMethodNoSuper_nolock源码

static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
    runtimeLock.assertLocked();
    assert(cls->isRealized());
    for (auto mlists = cls->data()->methods.beginLists(), 
              end = cls->data()->methods.endLists(); 
         mlists != end;
         ++mlists)
    {
        method_t *m = search_method_list(*mlists, sel);
        if (m) return m;//找到即return
    }
    return nil;
}

static method_t *search_method_list(const method_list_t *mlist, SEL sel)
{
    int methodListIsFixedUp = mlist->isFixedUp();
    int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
    
    if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) {
        return findMethodInSortedMethodList(sel, mlist);
    } else {
        // Linear search of unsorted method list(未排序方法列表的线性搜索)
        for (auto& meth : *mlist) {
            if (meth.name == sel) return &meth;
        }
    }
    return nil;
}

search_method_list,如果已经排序使用findMethodInSortedMethodList方法查找,这个方法二分查找;否则顺序查找,找到meth.name == sel即返回。

小结:class_getInstanceMethod从self中查找sel,并返回method_t。
我们都知道实例方法是存储在类方法中的,class_getInstanceMethod是在静态方法中调用的,即self是指当前类,符合实例方法是存储在类中的。

2.2 method_exchangeImplementations

void method_exchangeImplementations(Method m1, Method m2)
{
    if (!m1  ||  !m2) return;//m1,m2都不能为空

    mutex_locker_t lock(runtimeLock);
//本质交换了imp,name和types没变,不影响正常调用
    IMP m1_imp = m1->imp;
    m1->imp = m2->imp;
    m2->imp = m1_imp;
..
}

//方法数据结构
struct method_t {
    SEL name;//名称
    const char *types;//类型:v16@0:8
    MethodListIMP imp;//方法签名:如:(objc-test`-[Person addDynamicWalkInstance] at Person.m:36)
    //通过名称排序
    struct SortBySELAddress :
        public std::binary_function<const method_t&,
                                    const method_t&, bool>
    {
        bool operator() (const method_t& lhs,
                         const method_t& rhs)
        { return lhs.name < rhs.name; }
    };
};

这个方法适应与m1和m2都不能为空,仅交换了m1与m2的imp指针,这也是为什么交换后调用原来的方法名是调用交换后的方法。

2.3 class_getClassMethod

Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;
    return class_getInstanceMethod(cls->getMeta(), sel);//cls->getMeta()不是类对象,是元类
}

类的方法是存储在元类中的,class_getClassMethod是在静态方法调用的,cls指向的是当前类,通过cls->getMeta()找到元类。即交换类方法需要找到元类并交换。
类和实例方法的存储结构是相同的,只要找到对应的类和元类的cls,就能正确交换方法。

Class getMeta() {
        if (isMetaClass()) return (Class)this;
        else return this->ISA();
    }

getMeta如果是元类,直接返回this;否则返回对应的isa指针。

上一篇 下一篇

猜你喜欢

热点阅读