6--类的结构之cache

675 阅读2分钟

类的结构

image.png

image.png 当类第一次从磁盘加载到内存时的结构如下 image.png 第一次被使用时 image.png 将动态更新的部分提取出来,存入class_rw_ext_t image.png

最后类的整体结构如下. ro是clean memory,从被加载之后,就不会有变化, rw是dirty memory,用于存储动态修改的 image.png

cache分析

cache 是什么

通过指针地址平移的方式在lldb中打印出cache_t的内容, 如下是cache_t的定义,本文删除了一些不必要的代码

struct cache_t {
    explicit_atomic<struct bucket_t *> _buckets;
    explicit_atomic<mask_t> _mask;
    uint16_t _flags;
    uint16_t _occupied;
    };
struct bucket_t {
private:
    explicit_atomic<SEL> _sel;
    explicit_atomic<uintptr_t> _imp;
    };

image.png

下面是LLDB调试过程, image.png image.png 关于bucket_t存储到_buckets是通过hash值来存储的, 其中_occupied 指的是占用了几个,

unsigned cache_t::capacity()
{
    return mask() ? mask()+1 : 0; 
}

_mask = _bucket的个数 -1. 继续探索_buckets是怎么存入的,在源码中找到如下方法

image.png

下面是insert的流程图 image.png

LLDB 调用方法

当我们通过LLDB调用方法的时候,会发现cache中的内容可能不是符合我们预期的,LLDB 可能会增加一些函数的调用 image.png 我们在代码中调用了setHobby:, 在LLDB中调用了aaaName, 如果insert只是调用了两次,那么是不会进行扩容的, 可以看到当前的occupied = 2, mask = 7, 说明在调用aaaName的时候扩容了.

image.png

再次调用hobby, 发现occupied = 4, mask = 7, 依次输出buckets内容

image.png

发现buckets内容多了classrespondsToSelector:,这是通过LLDB调用方法增加的. 我们通过代码调用2个方法,到第三个的时候,就会扩容,通过如下源码

image.png 我们会发现buckets会将最后一个bucket_tIMP设置为指向第一个bucket, 所以当capacity为4的时候,只有3个是有效的.

补充 isKindOfClass & isMemberOfClass

image.png 上述的结果可能会觉得比较疑惑,那么上源码 image.png res1~res4调用的是类方法isKindOfClassisMemberOfClass

对res1分析
第一次循环tcls : 根元类, cls 是 NSObjet 不相等
第二次循环tcls:根元类的父类为NSObject 等于cls 所以res1 为YES

对res2分析
self->ISA 为根元类,与NSObject不等

对res3分析
第一次tcls:MLPerson 元类,不等
第二次tcls: MLPerson 元类的父类 为根元类,不等, 依次类推得出res3 不等

对res4分析
self->ISA 为MLPerson 元类,与MLPerson不等