美文网首页
OC对象的方法缓存

OC对象的方法缓存

作者: 小溜子 | 来源:发表于2020-09-03 10:39 被阅读0次

1、回顾

在程序运行的时候,oc对象在内存中的存储结构是objc_class类型的,objc_class存放着类的方法列表,属性列表,协议列表,成员变量列表
还存放着方法的缓存列表

2、方法缓存列表

思考:为什么要创建方法缓存列表,目的是什么

2.1 方法的调用

假如调用对象方法的时候,首先从对象的方法列表中查询方法,如果不存在,通过superclass查找父类的方法列表,直到找到方法。


image.png

按照这样的逻辑执行的话,每次调用方法都会去查询方法,这样会造成资源的浪费

加入存在一个方法的缓存列表,第一次调用方法的时候,都将这个方法缓存起来,那么下次再调用这个方法的时候,就可以直接从缓存列表中读取,不需要再按照流程去查询方法

image.png

再次调用使用过的方法的时候,直接从缓存列表中读取

3、缓存列表的内部结构 cache_t

// cache_t 内部主要有三个属性
struct cache_t {
    struct bucket_t *_buckets; // 是一个数组 散列表
    mask_t _mask; // 散列表的长度 - 1, 散列表的长度 >= 已经缓存的数量
    mask_t _occupied; // 已经缓存的方法数量
}

struct bucket_t {
private:
#if __arm64__ // 手机
     SEL _sel; // SEL作为key
    uintptr_t _imp;  // 函数的地址作为value
#else
    SEL _sel;  // SEL作为key
    uintptr_t _imp; // 函数的地址作为value
#endif
}
public:
// 获取sel
    inline SEL sel() const { return _sel; }
// 获取imp
    inline IMP imp() const {
        if (!_imp) return nil;
        return (IMP)
            ptrauth_auth_and_resign((const void *)_imp,
                                    ptrauth_key_process_dependent_code,
                                    modifierForSEL(_sel),
                                    ptrauth_key_function_pointer, 0);
    }

    template <Atomicity>
// 赋值 sel作为key imp作为value
    void set(SEL newSel, IMP newImp);
};

4、存储与查询方式

/// 查询方法的方式
//通过sel 方法名
bucket_t * cache_t::find(SEL s, id receiver)
{
    assert(s != 0);
   // 1.获取散列表
    bucket_t *b = buckets();
 // 2.获取_mask 散列表的长度 - 1
    mask_t m = mask();
// 3.方法名作为key ,cache_hash 内部是将sel & mask 获取位置下标
    mask_t begin = cache_hash(s, m);
// 4. i 存放的位置
    mask_t i = begin;
    do {
       ///如果 为空 或方法相同,返回
        if (b[i].sel() == 0  ||  b[i].sel() == s) {
            return &b[I];
        }
        // 如果方法不一致,将i -1 向上一个位置查询,直到返回结果
    } while ((i = cache_next(i, m)) != begin);

    // hack
    Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
    cache_t::bad_cache(receiver, (SEL)s, cls);
}

通过源码分析发现,苹果不是单纯的通过遍历的方式查询方法的位置。 而是通过将 sel 与 mask进行 &预算计算出 方法存放的下标,如果下标内已经存放了方法,会继续往上一个位置存放

如果散列表中分配的内存已经存满,会重新分配
void cache_t::expand()
{
    cacheUpdateLock.assertLocked();
   
    // 旧的数量
    uint32_t oldCapacity = capacity();
// 新的数量 扩容2倍
    uint32_t newCapacity = oldCapacity ? oldCapacity*2 : INIT_CACHE_SIZE;
    if ((uint32_t)(mask_t)newCapacity != newCapacity) {
        // mask overflow - can't grow further
        // fixme this wastes one bit of mask
        newCapacity = oldCapacity;
    }
  // 释放旧的空间,清空缓存列表,等待重新缓存
    reallocate(oldCapacity, newCapacity);
}

如果缓存列表满了,会清空缓存列表,扩容内存为原来的两倍,等待下次调用方法的时候重新进行缓存,因为 _mask 存放的数量 已经改变了,进行 &运算的时候,肯定是和之前的值不一样,所以需要清空,重新缓存

5、总结

方法缓存的存储方式 为散列表存储
1.通过方法名 & 当前的_mask 的数量 计算出下标
2.将方法存放到下标位置
3.如果当前位置已经存储了别的方法,那么将下标-1,向上存储
4.如果第一个位置也存储了,就冲最后一个位置继续
5.如果全部都满了,就会重新分配内存,重新进行缓存


image

相关文章

  • OC对象的方法缓存

    1、回顾 在程序运行的时候,oc对象在内存中的存储结构是objc_class类型的,objc_class存放着类的...

  • OC对象的方法缓存

    一、回顾 在程序运行的时候,oc对象在内存中的存储结构是objc_class类型的,objc_class存放着类的...

  • IOS精选面试题(三)

    OC调用一个方法底层做了哪些事 一:如果是调用实例方法,通过实例对象的isa指针找到类对象,在类对象的方法缓存(c...

  • OC底层方法的本质、查找流程

    1. 前言 前面的文章了解了OC对象的本质、类的本质以及方法缓存的原理,那么这篇文章将来分析一下OC方法底层的原理...

  • Lesson 0 Objective-C basic

    1.OC特性 (1)OC方法:对象方法(-),类方法(+) 1.对象方法:-(returnType)initWit...

  • React hooks useRef 缓存对象、缓存值

    1.缓存对象2.缓存值 1.缓存对象,拿到对象即可对对象操作,比如input的方法,video的方法等 2.缓存值...

  • 缓存

    flush方法 一级缓存 缓存的类型 对象缓存 数据缓存 save方法 会根据是不是临时对象转化过来的来判断执行u...

  • swift-基础-基本语法1

    创建对象: 'OC: alloc / initWithXXXSwift: (XXX:) 调方法OC [UICol...

  • From Objective-C to Ruby(4)-类和模块

    类 定义类 OC: ruby: 初始化方法 OC: ruby: 实例变量和属性 OC: ruby: 类方法和对象方...

  • ReactiveCocoa用法示例(二)

    知识点 RACSignal与OC对象方法的绑定 RACSignal与OC对象属性的绑定 [RACSignal me...

网友评论

      本文标题:OC对象的方法缓存

      本文链接:https://www.haomeiwen.com/subject/mrsnsktx.html