Runtime源代码解读4(方法列表)

2019-10-15数组

类的方法包括实例方法(instance method)和类方法(class method),二者保存在彻底不一样的地方,实例方法保存在类的方法列表中,类方法保存在元类的方法列表中。在Runtime源代码解读(实现面向对象初探)中已介绍过方法的基本数据结构objc_method,以及方法的基本响应链,本文介绍方法列表的具体实现原理。缓存

1、数据结构

Runtime源代码解读2(类和对象)中介绍过:类的class_ro_t包含method_list_t类型的baseMethodList成员;类的class_rw_t包含method_array_t类型的methods成员。二者一样保存方法列表,却使用不一样的数据结构,那么它们之间有什么关系呢?因为class_ro_t是保存的基本上是编译时决议的数据,baseMethodList显然是保存类定义时所定义的方法,这些方法是编译时决议的。而class_rw_tmethods实际上才是类的完整方法列表,并且class_rw_tmethods包含了class_ro_tbaseMethodListbash

类的class_rw_t保存方法列表保存形式并非简单的一维数组结构,而是二维数组结构,这是为了区分类构建阶段定义的基本方法,以及不一样分类之间的定义的方法。objc_class保存方法列表的数组结构是method_array_t类,method_array_t继承list_array_tt模板类。数据结构

1.1 list_array_tt 二维数组容器

Runtime 定义list_array_tt类模板表示二维数组容器。list_array_tt保存的数据主体是一个联合体,包含listarrayAndFlag成员,表示:容器要么保存一维数组,此时联合体直接保存一维数组的地址,地址的最低位必为0;要么保存二维数组,此时联合体保存二维数组的首个列表元素的地址,且最低位置为1。调用hasArray()方法能够查询容器是否保存的是二维数组,返回arrayAndFlag & 1,即经过最低位是否为1进行判断;调用list_array_ttarray()方法能够获取二维数组容器的地址,返回arrayAndFlag & ~1,即忽略最低位。函数

当联合体保存二维数组时,联合体的arrayAndFlag指向list_array_tt内嵌定义的array_t结构体。该结构体是简单的一维数组容器,但其元素为指向列表容器的指针,所以array_t的本质是二维数组。调用array_tbyteSize()能够返回列表容器占用的总字节数,为array_t结构体自己的size与保存列表元素的连续内存区块的size之和。post

// 二维数组容器模板
template <typename Element, typename List>
class list_array_tt {
      
    // 定义二维数组的外层一维数组容器
    struct array_t {
        uint32_t count;
        List* lists[0];

        static size_t byteSize(uint32_t count) {
            return sizeof(array_t) + count*sizeof(lists[0]);
        }

        // 计算容器占用字节数
        size_t byteSize() {
            return byteSize(count);
        }
    };

 protected:
    // 内嵌迭代器的定义(后文 1.1.1 节介绍)
    ...

 private:
    union {
        List* list;  // 要么指向一维数组容器
        uintptr_t arrayAndFlag;  // 要么指向二维数组容器
    };

    // 容器是否保存的是二维数组
    bool hasArray() const {
        return arrayAndFlag & 1;
    }

    // 获取容器保存的二维数组的地址
    array_t *array() {
        return (array_t *)(arrayAndFlag & ~1);
    }

    void setArray(array_t *array) {
        arrayAndFlag = (uintptr_t)array | 1;
    }

 public:
    // 计算方法列表二维数组容器中全部方法的数量
    uint32_t count() {
        uint32_t result = 0;
        for (auto lists = beginLists(), end = endLists(); 
             lists != end;
             ++lists)
        {
            result += (*lists)->count;
        }
        return result;
    }

    // 获取方法列表二维数组容器的起始迭代器
    iterator begin() {
        return iterator(beginLists(), endLists());
    }

    // 获取方法列表二维数组容器的结束迭代器
    iterator end() {
        List **e = endLists();
        return iterator(e, e);
    }

    // 获取方法列表二维数组容器包含的方法列表数量
    uint32_t countLists() {
        if (hasArray()) {
            return array()->count;
        } else if (list) {
            return 1;
        } else {
            return 0;
        }
    }

    // 获取方法列表二维数组容器的起始地址
    List** beginLists() {
        if (hasArray()) {
            return array()->lists;
        } else {
            return &list;
        }
    }

    // 获取方法列表二维数组容器的结束地址
    List** endLists() {
        if (hasArray()) {
            return array()->lists + array()->count;
        } else if (list) {
            return &list + 1;
        } else {
            return &list;
        }
    }

    void attachLists(List* const * addedLists, uint32_t addedCount) {
        // 后文 1.1.2 节介绍
        ...
    }

    void tryFree() {
        // 后文 1.1.2 节介绍
        ...
    }

    template<typename Result> Result duplicate() {
        // 后文 1.1.2 节介绍
        ...
    }
};
复制代码

1.1.1 list_array_tt 的内嵌迭代器

list_array_tt容器模板的内嵌迭代器iterator类的定义以下,包含三个成员:ui

  • lists:当前迭代到的数组的位置;
  • listsEnd:二维数组的外层容器的结尾;
  • m:当前迭代到的数组 中的元素的位置;
  • mEnd:当前迭代到的数组的结尾;

注意:构建list_array_tt的迭代器时,只能从方法列表到方法列表,不能从容器中的方法列表的某个元素的迭代器开始迭代。this

class iterator {
    List **lists;
    List **listsEnd;
    typename List::iterator m, mEnd;

 public:
    // 构建从begin指向的列表,到end指向的列表的迭代器
    iterator(List **begin, List **end) 
        : lists(begin), listsEnd(end)
    {
        if (begin != end) {
            m = (*begin)->begin();
            mEnd = (*begin)->end();
        }
    }

    const Element& operator * () const {
        return *m;
    }
    Element& operator * () {
        return *m;
    }

    bool operator != (const iterator& rhs) const {
        if (lists != rhs.lists) return true;
        if (lists == listsEnd) return false;  // m is undefined
        if (m != rhs.m) return true;
        return false;
    }

    //迭代时,若达到当前数组的结尾,则切换到下一个数组的开头
    const iterator& operator ++ () {
        assert(m != mEnd);
        m++;
        if (m == mEnd) {
            assert(lists != listsEnd);
            lists++;
            if (lists != listsEnd) {
                m = (*lists)->begin();
                mEnd = (*lists)->end();
            }
        }
        return *this;
    }
};
复制代码

1.1.2 list_array_tt 公开的操做方法

list_array_tt容器模板的三个操做类型的公开方法以下:编码

  • attachLists(...):将列表元素添加到二维数组容器的开头,注意到list_array_t没有定义构造函数,这是由于构造逻辑均在attachLists(...)中,包含容器从空转化为保存一位数组,再转化为保存二维数组的处理逻辑;
  • tryFree():释放二维数组容器占用内存;
  • duplicate(...):复制二维数组容器;
// 向二维数组容器中添加列表
void attachLists(List* const * addedLists, uint32_t addedCount) {
    if (addedCount == 0) return;

    if (hasArray()) {
        // 当容器中保存的是二维数组
        uint32_t oldCount = array()->count;
        uint32_t newCount = oldCount + addedCount;
        setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));  // 分配新内存空间从新构建容器
        array()->count = newCount;
        memmove(array()->lists + addedCount, array()->lists, 
                oldCount * sizeof(array()->lists[0]));  // 转移容器原内容到新内存空间
        memcpy(array()->lists, addedLists, 
               addedCount * sizeof(array()->lists[0]));  // 将须要新增的方法列表拷贝到新内存空间
    }
    else if (!list  &&  addedCount == 1) {
        // 当容器中无任何方法,直接将list成员指向addedLists
        list = addedLists[0];
    } 
    else {
        // 当容器中保存的是一维数组
        List* oldList = list;
        uint32_t oldCount = oldList ? 1 : 0;
        uint32_t newCount = oldCount + addedCount;
        setArray((array_t *)malloc(array_t::byteSize(newCount)));
        array()->count = newCount;
        if (oldList) array()->lists[addedCount] = oldList;
        memcpy(array()->lists, addedLists, 
               addedCount * sizeof(array()->lists[0]));
    }
}

// 释放容器占用的内存空间
void tryFree() {
    if (hasArray()) {
        for (uint32_t i = 0; i < array()->count; i++) {
            try_free(array()->lists[I]);
        }
        try_free(array());
    }
    else if (list) {
        try_free(list);
    }
}

// 拷贝容器
template<typename Result>
Result duplicate() {
    Result result;

    if (hasArray()) {
        array_t *a = array();
        result.setArray((array_t *)memdup(a, a->byteSize()));
        for (uint32_t i = 0; i < a->count; i++) {
            result.array()->lists[i] = a->lists[i]->duplicate();
        }
    } else if (list) {
        result.list = list->duplicate();
    } else {
        result.list = nil;
    }

    return result;
}
复制代码

1.2 method_array_t 方法列表二维数组容器

类的方法列表信息保存在class_rw_t结构体的methods成员中,类型为method_array_tmethod_array_t是按list_array_tt模板构建,以method_t(方法)为元素,以method_list_t(方法列表)为列表容器的二维数组容器,用于存储类的方法列表。method_list_t继承自entsize_list_tt顺序表模板,entsize_list_ttRuntime源代码解读2(类和对象) 介绍过,是具备固定类型元素的顺序表容器,注意到FlagMask指定为0x03,所以method_list_tentsizeAndFlags成员最低两位预留有特殊功能:若最低两位均为1,表示该方法列表已排序。spa

method_array_t中扩展list_array_tt的方法的方法名可见,method_array_t是为 category 量身定作的,显然 category 中定义的全部方法都存储在该容器中,每一个 category 定义的方法对应method_array_t二维数组容器中的一个元素,也就是一个方法列表method_list_t结构体的指针。扩展的方法以下:

  • beginCategoryMethodLists():指向容器的第一个数组;
  • endCategoryMethodLists(Class cls):当类的class_rw_t中不存在baseMethodList时,直接返回容器最后一个数组,当存在baseMethodList时,返回容器的倒数第二个数组。
static uint32_t fixed_up_method_list = 3;

// 方法列表
struct method_list_t : entsize_list_tt<method_t, method_list_t, 0x3> {
    // 查询方法列表是否已排序
    bool isFixedUp() const {
        return flags() == fixed_up_method_list;
    }
    
    // 标记方法列表已排序
    void setFixedUp() {
        runtimeLock.assertLocked();
        assert(!isFixedUp());
        entsizeAndFlags = entsize() | fixed_up_method_list;
    }

    // 新增返回方法在顺序表中的索引值的方法
    uint32_t indexOfMethod(const method_t *meth) const {
        uint32_t I = 
            (uint32_t)(((uintptr_t)meth - (uintptr_t)this) / entsize());
        assert(i < count);
        return I;
    }
};

// 方法列表二维数组容器
class method_array_t : 
    public list_array_tt<method_t, method_list_t> 
{
    typedef list_array_tt<method_t, method_list_t> Super;

 public:
    method_list_t **beginCategoryMethodLists() {
        return beginLists();
    }
    
    method_list_t **endCategoryMethodLists(Class cls) {
        method_list_t **mlists = beginLists();
        method_list_t **mlistsEnd = endLists();
    
        if (mlists == mlistsEnd  ||  !cls->data()->ro->baseMethods()) 
        {
            return mlistsEnd;
        }
    
        return mlistsEnd - 1;
    };

    method_array_t duplicate() {
        return Super::duplicate<method_array_t>();
    }
};

复制代码

Runtime源代码解读2(类和对象) 中介绍过类的加载过程。从 class realizing 时调用的methodizeClass(...)函数的处理逻辑能够看出:class_rw_t中的method_array_t容器保存了类的完整方法列表,包括静态编译的类的基本方法、运行时决议的 category 中的方法以及运行时动态添加的方法。并且class_rw_tmethod_array_t容器的最后一个数组实际上就是class_ro_tbaseMethodList

再结合 1.1.2 介绍的list_array_ttattachLists(...)方法逻辑,能够基本了解方法列表容器的工做机制。当使用class_addMethod(...)动态添加类,或者应用加载阶段加载 category 时,均调用了该方法。因为attachLists(...)添加方法时,将方法添加到容器的开头,将原有的method_list_t集体后移,所以类的同名方法的IMP的优先级从高到低排序以下:

  • 经过class_addMethod(...)动态添加的方法;
  • 后编译的类的 category 中的方法;
  • 先编译的类的 category 中的方法;
  • 类实现的方法;
  • 类的父类实现的方法;

类的方法列表的结构可总结以下图所示。其中绿色表示method_list_t容器,蓝色表示保存method_t结构体,黄色表示指向method_list_t结构体的指针。上图为method_array_t保存一维数组容器(method_list_t)时的内存结构,此时method_array_t的联合体list成员有效;下图为method_array_t保存二维数组容器时的内存结构,此时method_array_t的联合体arrayAndFlag成员有效。

当method_array_t的保存一位数组.jpg

当method_array_t保存二维数组.jpg

2、 动态添加方法的实现原理

运行时调用class_addMethod (...)函数能够给类动态添加方法,调用class_replaceMethod(...)能够替换方法的实现。实际上二者都调用了addMethod(...)函数,原理是先根据传入的方法名、方法IMP、类型编码构建method_t,而后新建method_list_t方法列表容器将method_t添加到其中,最后调用attachList(...)将方法列表添加到类的class_rw_tmethods方法列表二维数组容器中。

class_addMethod (...)的源代码以下:

// 添加方法
BOOL 
class_addMethod(Class cls, SEL name, IMP imp, const char *types)
{
    if (!cls) return NO;

    rwlock_writer_t lock(runtimeLock);
    return ! addMethod(cls, name, imp, types ?: "", NO);
}

// 替换方法实现
IMP 
class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
{
    if (!cls) return nil;

    rwlock_writer_t lock(runtimeLock);
    return addMethod(cls, name, imp, types ?: "", YES);
}

static IMP 
addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace)
{
    IMP result = nil;

    runtimeLock.assertWriting();

    assert(types);
    assert(cls->isRealized());

    method_t *m;
    if ((m = getMethodNoSuper_nolock(cls, name))) {
        // 若方法已经存在
        if (!replace) {
            result = m->imp;
        } else {
            result = _method_setImplementation(cls, m, imp);  // 设置方法IMP
        }
    } else {
        // 若方法不存在,则构建方法列表容器
        method_list_t *newlist;
        newlist = (method_list_t *)calloc(sizeof(*newlist), 1);
        newlist->entsizeAndFlags = 
            (uint32_t)sizeof(method_t) | fixed_up_method_list;
        newlist->count = 1;
        newlist->first.name = name;
        newlist->first.types = strdup(types);
        if (!ignoreSelector(name)) {
            newlist->first.imp = imp;
        } else {
            newlist->first.imp = (IMP)&_objc_ignored_method;
        }

        prepareMethodLists(cls, &newlist, 1, NO, NO);  // 后文 2.1 中详细介绍

        // 添加方法到类的class_rw_t的methods中
        cls->data()->methods.attachLists(&newlist, 1);  

        flushCaches(cls);  // 后文 2.2中详细介绍

        result = nil;
    }

    return result;
}

// 设置方法IMP
static IMP 
_method_setImplementation(Class cls, method_t *m, IMP imp)
{
    runtimeLock.assertWriting();

    if (!m) return nil;
    if (!imp) return nil;

    // 对于打开ARC的编译环境,忽略retain/release等MRC方法
    if (ignoreSelector(m->name)) {
        return m->imp;
    }

    IMP old = m->imp;
    m->imp = imp;

    flushCaches(cls);  // 清空cls类的方法缓冲

    updateCustomRR_AWZ(cls, m);  // 对retain/release等MRC方法,以及allocWithZone方法的特殊处理

    return old;
}

复制代码

向类动态添加方法,类的方法列表结构先后对比图示以下。其中红色标记部分为新增结构。处理过程为:将添加方法封装到method_list_t容器,而后插入到method_array_t的外层一位数组开头,其余元素总体后移,外层一位数组长度增长1

类动态插入方法方法先后列表结构图示.jpg

2.1 method_list_t 排序

addMethod(...)函数中还调用了prepareMethodLists(...)函数,该方法主要调用了fixupMethodList(...)函数。fixupMethodList(...)作了三件事情:

  • 调用sel_registerNameNoLock函数将方法列表中全部方法的SEL注册到内存,注册的内存实际是以 方法名字符串 为键值,将SEL插入到系统维护的namedSelectors哈希表中;
  • 对方法列表内的全部方法进行排序;
  • 将方法列表状态标记为isFixedUp,表示方法列表已排序;

之因此对方法列表排序,是为了使方法列表支持二分查找,从而下降经过SEL在方法列表中搜索IMP的时间复杂度。prepareMethodLists(...)函数相关代码以下:

static void 
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount, 
                   bool baseMethods, bool methodsFromBundle)
{
    if (addedCount == 0) return;

    for (int i = 0; i < addedCount; i++) {
        method_list_t *mlist = addedLists[I];
        assert(mlist);

        // Fixup selectors if necessary
        if (!mlist->isFixedUp()) {
            fixupMethodList(mlist, methodsFromBundle, true/*sort*/);
        }
    }
}

static void 
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
    for (auto& meth : *mlist) {
        const char *name = sel_cname(meth.name);
        
        // 注册方法列表中的方法名
        SEL sel = sel_registerNameNoLock(name, bundleCopy); 
        meth.name = sel;
        
        if (ignoreSelector(sel)) {
            meth.imp = (IMP)&_objc_ignored_method;
        }
    }
    
    // 按照SEL对方法列表排序
    if (sort) {
        method_t::SortBySELAddress sorter;
        std::stable_sort(mlist->begin(), mlist->end(), sorter);
    }
    
    // 标记方法列表已经排好序并固定。
    // method_list_t的entsizeAndFlags最低两位均为1表示方法列表已经排好序并固定
    mlist->setFixedUp();
}
复制代码

2.2 方法缓冲

所谓方法缓冲(cache),是使用哈希表保存类的最近被调用的方法,利用哈希表的 O(1) 的查询时间复杂度提升方法调用的效率。objc_classcache_t cache成员就是类的方法缓冲。cache_t结构体代码以下。其实是指向一个能够动态扩容的 Key-Value 形式的哈希表,以方法名SEL为Key,以方法IMP为Value,分别对应bucket_t结构体的_sel_imp成员。cache_t结构体包含如下成员:

_buckets:保存哈希表全部元素的数组,元素类型为bucket_t结构体,不是指向bucket_t的指针; _mask:间接表示哈希表的容量,为所有位为1的二进制数。哈希表容量最小为4,知足公式:哈希表容量 = _mask + 1。扩容时设置_mask =(_mask + 1) & _mask_occupied:哈希表实际缓存的方法个数;

前文addMethod(...)的代码中,调用flushCache(...)时用于清空指定类的方法缓冲。之因此清空是由于addMethod(...)可能会触发方法IMP变动,必须保证方法缓冲中保存的全部方法SELIMP映射的正确性。关于方法缓冲的具体细节见 Runtime 源代码objc-cache.mm不深刻介绍。

// 方法缓冲哈希表的元素,_sel为key,_imp为value
struct bucket_t {
private:
#if __arm64__
    uintptr_t _imp;
    SEL _sel;
#else
    SEL _sel;
    uintptr_t _imp;
#endif

    ...

public:

    ...

};

// 方法缓冲的数据结构
struct cache_t {
    struct bucket_t *_buckets;  //保存哈希表全部元素的数组
    mask_t _mask;  // 间接表示哈希表的容量
    mask_t _occupied;  // 

public:
    struct bucket_t *buckets();
    mask_t mask();
    mask_t occupied();
    void incrementOccupied();
    void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
    void initializeToEmpty();

    mask_t capacity();
    bool isConstantEmptyCache();
    bool canBeFreed();

    static size_t bytesForCapacity(uint32_t cap);
    static struct bucket_t * endMarker(struct bucket_t *b, uint32_t cap);

    void expand();
    void reallocate(mask_t oldCapacity, mask_t newCapacity);
    struct bucket_t * find(cache_key_t key, id receiver);

    static void bad_cache(id receiver, SEL sel, Class isa) __attribute__((noreturn));
};
复制代码

3、方法的完整响应流程

调用方法经常使用的方式包括:

  • @interface定义的公开接口;
  • NSObjectperformSelector系列方法;
  • NSInvocation类触发;
  • objc_msgSend(...)消息发送函数;

注意:<objc/message.h>公开的接口形式是无参的,能够尝试用相似BOOL (*msg)(Class, SEL, id) = (typeof(msg))objc_msgSend处理,作强制转换后再调用msg函数指针以实现正常传参,第一个参数是接收消息的对象(实例方法)/(类方法),第二个参数是SEL方法选择器,后续可变长参数列表为方法的参数列表

形式各有不一样,可是本质上是作一样的事情:首先转化为objc_msgSend(...)消息发送的形式,而后经过 targetselector 定位到方法的IMP,最后调用IMP指向的函数指针。因而关键点来到了方法IMP定位上。从class_getMethodImplementation(Class cls, SEL sel)源代码能够知道 Runtime 如何经过方法名SEL定位 方法的实现IMP。代码以下:

IMP class_getMethodImplementation(Class cls, SEL sel)
{
    IMP imp;

    if (!cls  ||  !sel) return nil;

    imp = lookUpImpOrNil(cls, sel, nil, 
                         YES/*initialize*/, YES/*cache*/, YES/*resolver*/);

    // 继承链无该SEL对应的IMP,则从消息转发流程中搜索IMP
    if (!imp) {
        return _objc_msgForward;
    }

    return imp;
}

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

复制代码

最后定位到关键代码lookUpImpOrForward(...),顾名思义是用于在类中查找IMP,或者进入转发。根据代码逻辑,总结方法根据方法名SEL定位方法的实现IMP的主要处理流程以下:

  1. 在类的方法缓冲中搜索方法,搜索到则马上返回;
  2. 判断类是否已完成 class realizing,若未完成,则先进行 class realizing,由于在class realizing 完成以前,类的class_rw_t中的方法列表都有多是不完整的;
  3. 若类未初始化,则先执行类的initialize()方法,由于其中可能包含动态添加方法逻辑;
  4. 再次在类的方法缓冲中搜索方法,搜索到则马上返回,由于均可能触发方法缓冲更新;
  5. 遍历类的method_array_t方法列表二维数组容器中的全部method_list_t,在method_list_t中搜索方法。若method_list_t已排序,则使用二分法搜索,若method_list_t未排序,则用简单线性搜索。搜索到则马上返回;
  6. 沿着继承链在父类中递归地搜索方法。搜索到则马上返回;
  7. 尝试方法动态解析(在 3.1 中详细介绍),若返回true,则回到第4步再次尝试搜索;
  8. 若方法动态解析返回false,则触发消息转发流程。返回_objc_msgForward_impcache,返回该IMP表示须要进入消息转发流程;

注意:initialize()方法和load()方法的做用有点类似,都是用于类的初始化。但二者在行为上有很大区别:一、load()方法不具备继承特性,继承链上的全部类定义的全部load()方法均会被调用,load()方法中禁止使用super关键字;intialize()具备继承特性,可以使用super关键字,其行为至关于普通的类方法。二、load()方法在类加载时触发,且 runtime 严格控制一个类的load()方法只会被执行一次;initialize()方法 在第一次调用类的方法时,在lookUpImpOrForward(...)中检查类是否初始化时触发,所以若父类实现了initialize()方法,可是其全部子类均未实现,则不管是第一次调用子类仍是父类的方法,都会触发父类的initialize()方法。

lookUpImpOrForward(...)的源代码看起来很长,可是基本上每一步都包含很关键的信息,对理解消息的响应流程很是重要。所以只删除代码中读写锁、调试相关代码,在代码中作了详细的注释。

// Runtime预约义的统一的触发方法动态解析的SEL,注意实际值应该并非NULL
SEL SEL_resolveInstanceMethod = NULL;

// Runtime预约义的标记进入消息转发流程的IMP
extern void _objc_msgForward_impcache(void);

// 从cls类的`cache_t`方法缓存哈希表中查询SEL对应的IMP
extern IMP cache_getImp(Class cls, SEL sel);

// 在cls类查询SEL对应的IMP
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    Class curClass;
    IMP imp = nil;
    Method meth;
    bool triedResolver = NO;

    // 在方法缓冲中搜索方法
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }

    // 方法固定相关操做
    if (!cls->isRealized()) {
        realizeClass(cls);
    }

    // 若类的initialize()方法未调用,则先进行类的initialize初始化过程
    if (initialize  &&  !cls->isInitialized()) {
        _class_initialize (_class_getNonMetaClass(cls, inst));  //调用类的initialize()方法
    }

 retry:
    // 若ARC编译选项打开则须要忽略retain、release等方法,忽略
    if (ignoreSelector(sel)) {
        imp = _objc_ignored_method;
        cache_fill(cls, sel, imp, inst);
        goto done;
    }

    // 在类的cache_t方法缓存哈希表中查询SEL对应的IMP
    imp = cache_getImp(cls, sel);
    if (imp) goto done;

    // 在类的method_array_t方法列表容器中查询SEL对应的IMP
    meth = getMethodNoSuper_nolock(cls, sel);
    if (meth) {
        log_and_fill_cache(cls, meth->imp, sel, inst, cls);
        imp = meth->imp;
        goto done;
    }

    // 沿着类的继承链循环。在各级类及父类的方法缓冲、方法列表容器中查询SEL对应的IMP
    curClass = cls;
    while ((curClass = curClass->superclass)) {
        // 在类的方法缓存中查询
        imp = cache_getImp(curClass, sel);
        if (imp) {
            if (imp != (IMP)_objc_msgForward_impcache) {
                // 若搜索到IMP,则将IMP写入父类的方法缓存
                log_and_fill_cache(cls, imp, sel, inst, curClass);
                goto done;
            }
            else {
                break;
            }
        }

        // 在类的方法列表中查询
        meth = getMethodNoSuper_nolock(curClass, sel);
        if (meth) {
            log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
            imp = meth->imp;
            goto done;
        }
    }

    // 尝试动态解析方法
    if (resolver  &&  !triedResolver) {
        runtimeLock.unlockRead();
        _class_resolveMethod(cls, sel, inst);

        triedResolver = YES;
        goto retry;
    }

    // 触发消息转发流程
    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

 done:
    return imp;
}

// 当前类的方法列表中搜索方法
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
    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 nil;
}

// 简单线性遍历method_list_t搜索方法名为sel的方法,查到马上返回
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)) {
        //对于isFixedUp的方法列表用二分法搜索,这也是fixedUp时须要对方法列表排序的缘由
        return findMethodInSortedMethodList(sel, mlist);
    } else {
        // 线性搜索,简单遍历
        for (auto& meth : *mlist) {
            if (meth.name == sel) return &meth;
        }
    }
    return nil;
}

// 用二分查找从已排序的method_list_t中搜索方法名为key的方法
static method_t *findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
    assert(list);

    const method_t * const first = &list->first;
    const method_t *base = first;
    const method_t *probe;
    uintptr_t keyValue = (uintptr_t)key;
    uint32_t count;
    
    for (count = list->count; count != 0; count >>= 1) {
        probe = base + (count >> 1);
        
        uintptr_t probeValue = (uintptr_t)probe->name;
        
        if (keyValue == probeValue) {
            while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
                probe--;
            }
            return (method_t *)probe;
        }
        
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }
    
    return nil;
}
复制代码

3.1 方法动态解析及消息转发

当类及其继承链的方法列表不存在SEL对应的方法时,会进入方法动态解析 以及 消息转发流程,Runtime 只公布了触发逻辑,实现细节则彻底隐藏在objc_msgSend的实现中。触发方法动态解析经过objc_msgSend向类发送SEL_resolveInstanceMethod消息,推测objc_msgSend内部只是直接调用类的resolveInstanceMethodresolveClassMethod方法查询SEL是否有动态解析,有则返回true反之返回false

所谓动态方法解析实际上就是对不经常使用到的方法,在正式调用到该方法时才将方法添加到类的方法列表。所以,resolveInstanceMethodresolveClassMethod中的代码通常是对须要动态解析的SEL,调用class_addMethod(...)动态添加该SEL对应的方法,并返回YES。若是只是返回YES的话,而不添加方法极可能形成的后果是responseToSelector虽然能够返回YES,可是performSelector时仍然抛出unrecognized selector异常。

方法动态解析相关代码以下。

// 动态解析方法
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
    if (! cls->isMetaClass()) {
        // 动态解析实例方法
        _class_resolveInstanceMethod(cls, sel, inst);
    } 
    else {
        // 动态解析类方法
        _class_resolveClassMethod(cls, sel, inst);
        
        // 这里为何要给元类再动态解析实例方法
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            _class_resolveInstanceMethod(cls, sel, inst);
        }
    }
}

// 动态解析类方法
static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
    // 若类未实现resolveClassMethod方法,则不走方法动态解析流程
    if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    // 触发方法动态解析流程
    bool resolved = msg(_class_getNonMetaClass(cls, inst), 
                        SEL_resolveClassMethod, sel);

    // lookUpImpOrNil触发将动态解析的SEL与IMP的映射添加到类的方法缓存
    // 这样下次调用SEL时可直接从cache_t中查询到IMP
    IMP imp = lookUpImpOrNil(cls, sel, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
    }
}

// 动态解析实例方法
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
    // 若类未实现resolveInstanceMethod方法,则不走方法动态解析流程
    if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    // 触发方法动态解析流程
    bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);

    // lookUpImpOrNil触发将动态解析的SEL与IMP的映射添加到类的方法缓存
    // 这样下次调用SEL时可直接从cache_t中查询到IMP
    IMP imp = lookUpImpOrNil(cls, sel, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
}
复制代码

注意:完成动态解析后,又当即调用了lookUpImpOrNil(...),其目的是将resolveInstanceMethodresolveClassMethod方法实现代码中为响应SEL而动态添加的IMP加入到类的方法缓冲,则下一次调用可直接从方法缓冲中获取。

关于方法动态解析和消息转发流程的内部实现代码,Runtime 公开的源代码并很少。尤为是消息转发流程的实现,基本隐藏在objc_msgSend函数的内部实现中。可是网上有不少关于 Runtime 消息转发机制的博文,这里只贴出方法动态解析及消息转发流程的大体流程再也不赘述。图中从左到右第一列是指,在类的继承链上存在响应方法;第二列是进入方法动态解析流程(method resolution);第三列是进入消息快速转发流程(fast forwarding);第四列是进入消息完整转发流程(normal forwarding)。

消息动态解析及转发流程.jpg

4、总结

  • class_ro_tbaseMethodList只保存了类的基本方法列表,只包含类定义时定义的方法,这些方法都是编译时决议的; 类的class_rw_tmethods保存了类的完整方法列表,除了包含基本方法列表外,还包含运行时决议的,类的分类中定义的方法以及动态添加的方法;

  • class_rw_tmethods使用method_array_t二维数组容器保存,包含一个或多个method_list_t结构体,其中method_array_t的外层一位数组容器的最后一个元素为指向class_ro_tbaseMethodList的指针;

  • 类的 class realizing 阶段载入方法列表时,须要对全部方法列表根据SEL进行排序,这是为了在搜索方法时能根据SEL进行二分法搜索提升方法搜索效率;

  • 调用class_addMethod(...)添加方法时,将方法封装成method_list_t,并添加到class_rw_tmethods的开头,其余元素总体后移;

  • 类的方法缓冲缓存最近调用方法的SELIMP的映射,是可动态扩容的 Key-Value 形式的哈希表,可经过方法SEL查找IMP,引入方法缓冲可提升方法搜索的效率;

  • 调用方法的本质是经过objc_msgSend向 target 发送 selector 消息,target 响应消息时,前后在类的方法缓冲、类的方法列表、继承链上全部类的方法缓冲和方法列表中搜索方法IMP,若方法无响应,则触发方法动态解析过程,执行resolveInstanceMethodresolveClassMethod中的方法动态解析逻辑,若方法动态解析过程无响应,则进入消息转发流程;

  • 下一篇介绍属性列表的实现。

相关文章
相关标签/搜索