06-将手伸进objc_class中的cache, 看看我们调用的方法是如何缓存的?


  • 我们都知道OC中属性存储数据信息的, 方法的功能修改属性的数据.
  • 在前面我们分析过objc_class结构体(里面存储类的信息), 里面有继承过来的isa(指向元类), 有superClass, 有bits(存储属性, 实例方法, 代理, ro里有成员变量)结构体
  • cache结构体里面存储的是什么呢?

1: 我们先根据源码梳理下objc_class的结构图

objc_class结构树-c1342

2: 接下来我们来通过指针偏移试着看看cache里存储的是什么

cache查找LLDB流程, 图是月月的!

关于缓存占用量的计算,有以下几点说明:

  • buckets() 是个列表, 怎么查找多个呢? 图是月月的!

    • 利用指针偏移, 数组的首地址第一个元素的地址

    例: *($4 + 1)

    • 利用列表特性

    例: $3.buckets()[1]

  • 经过lldb打印查看, 我们可以确认cache里存储的是方法缓存

  • alloc申请空间时,此时的对象已经创建,调用的实例方法,都是shioccupied+1, 扩容后会清空buckets, occupied为置为0

  • 当有属性赋值时,会隐式调用set方法,occupied也会增加


3: 我们详细的看下cache的结构

struct cache_t {//只复制了部分重要信息
//CACHE_MASK_STORAGE_OUTLINED: 模拟器 or macOS环境
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    // explicit_atomic: 原子性, 保证cache增删改差的线程安全
    // 等同于struct bucket_t * _buckets;
    // _buckets: 存放imp和sel
    explicit_atomic<struct bucket_t *> _buckets;
    explicit_atomic<mask_t> _mask; //掩码
//CACHE_MASK_STORAGE_HIGH_16: 64位真机
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    // 真机环境中, buckets和mask掩码存储在一起, 掩码在高16位(通过 << maskShift), buckets 存在剩余位
    explicit_atomic<uintptr_t> _maskAndBuckets;
    mask_t _mask_unused;//暂时没有用到, 猜测没开发完, 不管它
    
    static constexpr uintptr_t maskShift = 48;
    
    //掩码后的其他位必须为零。 msgSend
    //利用这些附加位来构造值
    //在一条来自_maskAndBuckets的指令中`mask << 4`。
    static constexpr uintptr_t maskZeroBits = 4;
    
    // The largest mask value we can store.
    static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
    
    // 应用于`_maskAndBuckets`的掩码,以获取存储桶指针。
    static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << (maskShift - maskZeroBits)) - 1;
    
    // 确保我们有足够的位用于存储桶指针。
    static_assert(bucketsMask >= MACH_VM_MAX_ADDRESS, "Bucket field doesn't have enough bits for arbitrary pointers.");
//非64为真机, 因为iOS9之后废弃32位, 所以我们不研究它
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    // _maskAndBuckets stores the mask shift in the low 4 bits, and
    // the buckets pointer in the remainder of the value. The mask
    // shift is the value where (0xffff >> shift) produces the correct
    // mask. This is equal to 16 - log2(cache_size).
    explicit_atomic<uintptr_t> _maskAndBuckets;
    mask_t _mask_unused;

    static constexpr uintptr_t maskBits = 4;
    static constexpr uintptr_t maskMask = (1 << maskBits) - 1;
    static constexpr uintptr_t bucketsMask = ~maskMask;
#else
#error Unknown cache mask storage type.
#endif

#if __LP64__
    uint16_t _flags;
#endif
    uint16_t _occupied;

public:
    static bucket_t *emptyBuckets();
    //重点可以获取buckets列表
    struct bucket_t *buckets();
    mask_t mask();// 获取我们的掩码(也可以理解为开辟最大空间)
    mask_t occupied();// 记录当前缓存的方法数量
    void incrementOccupied();// 操作`occupied++`, 即新插入一个bucket
    void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
    void initializeToEmpty();

    unsigned capacity();// 容量
    
    static struct bucket_t * endMarker(struct bucket_t *b, uint32_t cap);
    // 重新开辟空间, 一般在内存满3/4时扩容后调用
    void reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld);
    // 插入新的bucket
    void insert(Class cls, SEL sel, IMP imp, id receiver);


4: 总结: 我们来梳理下cache的工作流程

cache 缓存bucket流程图

疑问解答 --Style_月月

  • 1、_mask是什么?

    _mask是指掩码数据,用于在哈希算法或者哈希冲突算法(cache_next)中计算哈希 下标,其中mask等于capacity(内存总容量) - 1

  • 2、_occupied 是什么?

    • _occupied表示哈希表中 sel-imp 的占用大小 (即可以理解为分配的内存中已经存储了sel-imp的的个数),

    • alloc后, 调用的实例方法都会导致occupied变化, 包括属性隐式实现的set方法

  • 3、为什么随着方法调用的增多,其打印的occupied 和 mask会变化?

    因为在cache第一次缓存bucket时,分配的空间是4个,随着方法调用的增多,当存储的bucket个数 超过 capacity(总容量)的3/4, 就会进行capacity翻倍, 并清理旧缓存, 之后继续缓存新调用的实例方法.

  • 4、bucket数据为什么会有丢失的情况?,例如2-7中,只有say3、say4方法有函数指针

    原因是在扩容时,是将原有的内存全部清除了,再重新申请了内存导致的, 见疑问3解答

  • 5、2-7中say3、say4的打印顺序为什么是say4先打印,say3后打印,且还是挨着的,即顺序有问题?

    因为bucket的存储是通过哈希算法-cache_hash计算下标的,其计算的下标有可能已经存储了sel,所以又需要通过哈希冲突-cache_next算法重新计算哈希下标,所以下标并不是固定

    cache_hash实现-c739

cache_next实现-c739

cache缓存bucket流程图.jpg
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272