OC objc_msgSend方法缓存的查找(快速查找)

332 阅读3分钟

objc_msgSend 中,方法缓存的查找是通过哈希表(Hash Table)实现的。每个类(Class)都有一个方法缓存(Method Cache),用于存储最近使用的方法实现。使用哈希表可以快速地在缓存中查找方法实现,这大大提高了方法调用的效率。

下面我们详细分析方法缓存查找算法,并结合源码进行重点注释。

方法缓存 (Method Cache) 结构

方法缓存是一个哈希表,存储了方法的选择器(Selector)和方法的实现指针(IMP)。在 Objective-C 的运行时库中,方法缓存的结构定义如下:

struct cache_t {
    // 指向缓存条目的指针
    struct bucket_t *buckets;
    
    // 缓存的容量
    mask_t mask;
    
    // 缓存中的条目数
    mask_t occupied;

    // 在缓存中查找方法实现
    inline IMP find(SEL selector) {
        // 获取哈希值
        mask_t index = (mask_t)selector & mask;

        // 遍历哈希表
        for (mask_t i = 0; i <= mask; i++) {
            // 获取当前索引
            mask_t currIndex = (index + i) & mask;

            // 获取缓存条目
            bucket_t *bucket = &buckets[currIndex];

            // 检查选择器是否匹配
            if (bucket->sel == selector || bucket->sel == NULL) {
                return bucket->imp;
            }
        }

        // 如果在缓存中未找到实现,返回 NULL
        return NULL;
    }
};

objc_msgSend 中的方法缓存查找

objc_msgSend 函数中,首先会在方法缓存中查找方法实现。下面是相关代码及注释:

// objc_msgSend 函数负责消息发送
void objc_msgSend(id self, SEL _cmd, ...) {
    // 获取类对象
    Class cls = object_getClass(self);
    
    // 获取方法缓存
    cache_t *cache = &cls->cache;
    
    // 在缓存中查找方法实现
    IMP imp = cache->find(_cmd);

    // 如果找到方法实现,直接返回实现的指针
    if (imp) {
        return imp;
    }

    // 如果缓存中没有找到方法实现,调用未缓存的消息发送函数
    return _objc_msgSend_uncached(self, _cmd);
}

方法缓存查找算法

方法缓存查找的具体实现是在 cache_t 结构体中的 find 方法中。下面是 find 方法的详细注释:

struct cache_t {
    struct bucket_t *buckets;
    mask_t mask;
    mask_t occupied;

    // 在缓存中查找方法实现
    inline IMP find(SEL selector) {
        // 获取哈希值,使用选择器地址与缓存掩码进行与操作
        mask_t index = (mask_t)selector & mask;

        // 遍历哈希表,线性探测
        for (mask_t i = 0; i <= mask; i++) {
            // 计算当前索引
            mask_t currIndex = (index + i) & mask;

            // 获取当前缓存条目
            bucket_t *bucket = &buckets[currIndex];

            // 如果选择器匹配,返回方法实现
            if (bucket->sel == selector) {
                return bucket->imp;
            }

            // 如果选择器为空,表示未命中,返回 NULL
            if (bucket->sel == NULL) {
                return NULL;
            }
        }

        // 如果遍历完所有条目仍未找到实现,返回 NULL
        return NULL;
    }
};

解释说明

  1. 哈希计算

    • 使用选择器地址与缓存掩码(mask)进行与操作,得到初始索引。这是基于选择器地址的简单哈希函数。
  2. 线性探测

    • 采用线性探测法(Linear Probing)来解决哈希冲突。即如果初始位置被占用,则继续探测下一个位置,直到找到匹配的选择器或遇到空位(表示未命中)。
  3. 比较选择器

    • 在查找过程中,如果选择器匹配,则返回对应的方法实现指针(IMP)。
    • 如果选择器为空,表示查找未命中,返回 NULL

小结

通过以上分析可以看出,objc_msgSend 函数在方法缓存中查找方法实现时,采用了基于哈希表的查找算法,并结合线性探测来解决哈希冲突。这种方式能够在大多数情况下快速找到方法实现,从而提高方法调用的效率。

源码中的这些注释和详细说明,有助于我们更好地理解 Objective-C 运行时库中方法缓存查找的具体实现机制。