类的结构
当类第一次从磁盘加载到内存时的结构如下
第一次被使用时
将动态更新的部分提取出来,存入
class_rw_ext_t
最后类的整体结构如下. ro是clean memory,从被加载之后,就不会有变化, rw是dirty memory,用于存储动态修改的
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;
};
下面是LLDB调试过程,
关于
bucket_t存储到_buckets是通过hash值来存储的, 其中_occupied 指的是占用了几个,
unsigned cache_t::capacity()
{
return mask() ? mask()+1 : 0;
}
_mask = _bucket的个数 -1. 继续探索_buckets是怎么存入的,在源码中找到如下方法
下面是insert的流程图
LLDB 调用方法
当我们通过LLDB调用方法的时候,会发现cache中的内容可能不是符合我们预期的,LLDB 可能会增加一些函数的调用
我们在代码中调用了
setHobby:, 在LLDB中调用了aaaName, 如果insert只是调用了两次,那么是不会进行扩容的, 可以看到当前的occupied = 2, mask = 7, 说明在调用aaaName的时候扩容了.
再次调用hobby, 发现occupied = 4, mask = 7, 依次输出buckets内容
发现buckets内容多了class 和 respondsToSelector:,这是通过LLDB调用方法增加的.
我们通过代码调用2个方法,到第三个的时候,就会扩容,通过如下源码
我们会发现
buckets会将最后一个bucket_t的IMP设置为指向第一个bucket, 所以当capacity为4的时候,只有3个是有效的.
补充 isKindOfClass & isMemberOfClass
上述的结果可能会觉得比较疑惑,那么上源码
res1~res4调用的是类方法isKindOfClass 和 isMemberOfClass
对res1分析
第一次循环tcls : 根元类, cls 是 NSObjet 不相等
第二次循环tcls:根元类的父类为NSObject 等于cls 所以res1 为YES
对res2分析
self->ISA 为根元类,与NSObject不等
对res3分析
第一次tcls:MLPerson 元类,不等
第二次tcls: MLPerson 元类的父类 为根元类,不等, 依次类推得出res3 不等
对res4分析
self->ISA 为MLPerson 元类,与MLPerson不等