一、简化源码
objc源码的cache_t定义
cache_t的源码还是比较繁琐的,阅读起来比较费劲,首先我们来精简源码,让我们更容易把握住cache_t结构的主要脉络,而不是迷失在细节中。这可能是我们刚开始阅读源码最容易遇到的障碍。
首先cache_t是一个结构体,那么我么排除static静态变量,他们是不存在结构体中的。接下来我们排除struct中定义的方法。我们得到cache_t有效成员结构如下:
```
struct cache_t {
explicit_atomic<uintptr_t> _bucketsAndMaybeMask;// 8
union{
struct{
explicit_atomic<mask_t> _maybeMask;// 4
#if __LP64__
uint16_t _flags; // 2
#endif
uint16_t _occupied;// 2
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache;// 8
};
}
```
看起来是不是还有点啰嗦,继续简化。
1.1、explicit_atomic<>看着就奇奇怪怪,能不能干掉?
修饰把normal指针转化为atomic指针,大小不变,有对应的store 和 load方法,应该就是atomic的setter和getter,lock free的时候表现跟normal相同。nice,看来可以把它干掉了。
1.2、union的preopt_cache_t *和struct能不能干掉一个,干掉哪个?
dyld才会用到的成员变量,我们主要研究的是什么,是类的cache_t成员,那么干掉他
1.3、__LP64 64位系统,现在基本都是64位的保留,最终得到cache_t如下:
struct cache_t {
uintptr_t _bucketsAndMaybeMask;//8
_bucketsAndMaybeMask is a buckets_t pointer
mask_t _maybeMask;//4 _maybeMask is the buckets mask
uint16_t _flags;//2 标记位,缓存一些方法信息
uint16_t _occupied;//2 buckets内元素的个数
}
把cache_t扒的干干净净,再看,是不是很简洁很爽。😁,接下来我们来研究一下cache_t的每个成员,他们的作用以及他们是怎么工作的。
二、_bucketsAndMaybeMask
关于_bucketsAndMaybeMask我们看到有俩中注释
我们在真机验证的中发下,_maybeMask是被使用,即第一种情况。_bucketsAndMaybeMask在iOS真机模拟器环境中是一个纯粹的buckets_t pointer,即buckets_t *,bucket_t的数组,用来存储方法信息,那么它就是一个简单的数组吗?
继续完善我们的cache_t成员结构
struct cache_t {
buckets_t * _bucketsAndMaybeMask;//8
mask_t _maybeMask;//4
uint16_t _flags;//2
uint16_t _occupied;//2
}
2.1、buckets_t的数据结构
bucket_ty源码
按照我们简化代码的思路继续:
struct bucket_t{
uintptr_t _imp;
SEL _sel;
}
现在是不是一目了然。
2.2、_bucketsAndMaybeMask是怎么工作的呢,他为什么能提高方法的查询效率呢?
增删改查,我们去找一下的查找方法
作为一个经常被调用的方法,cacheLookup是用汇编实现的
虽然我们对汇编代码不太熟悉,但是看注释我们很显然看到index是通过sel计算得到,然后meth = cache->buckets[index],看到这里,对哈希表熟悉的同学是不是明白了cache_t查找方法更快?对,cache->buckets是一个哈希表,方法存储的位置是通过sel计算的得到的,时间复杂度是O(1),典型的空间换时间。
我们继续看一下insert方法,方法缓存是如何添加的
insert核心逻辑
看起来代码不少,核心步骤就3件事
1.确保buckets有足够缓存空间
buckets为空,reallocate(oldCapacity,capacity,false)开辟缓存空间
buckets的newOccupied + 1 <= capacity *3/4 空间够用什么也不做
buckets的newOccupied + 1 > capacity *3/4 ,就reallocate(oldCapacity,capacity*2,false)二倍扩容
old内存被free释放掉,里面的数据并没有迁移到newBuckets
2.cache_hash(sel)计算插入位置
sel注册在符号表中,他的地址值在程序运行期间可以看作是一个常量
3.循环查找插入位置
b[i]为空插入 return
b[i].sel() == sel return
其他情况 i + 1
&mask 是i+1>mask取余从0再开始查找位置,知道i==begin,结束循环
三、_maybeMask/_occupied/_flags
_maybeMask 记录buckets的容量
_occupied 记录buckets已存方法个数
_flag这个比较特殊
一个2字节的_flags,小小的身体却存储了很多信息,是否有c++构造函数,析构函数,fastInstanceSize,REQUIRES_RAW_ISA,HAS_DEFAULT_AWZ,HAS_DEFAULT_CORE
苹果对_flags的使用真是勤俭持家的代表,每一位都有意义
四、跑一跑代码
测试代码,验证我们精简源码思路,抓住主要脉络的思路是否正确,把系统类型强转为精简后类型然后打印
正常输出,符合预期
五、cache_t总结
提炼过的cache_t的数据结构及insert方法流程
本篇我们从哪些可以简化、为什么可以简化开始,逐步简化源码搞清楚了cache_t的主要脉络。其中最重要的是bucket_t * _bucketsAndMaybeMask这个指针,它指向缓存方法的哈希数组,存储bucket_t 数据成员。当然_bucketsAndMaybeMask在不同的编译分支(#if #elif)中,也可以通过掩码存取更多信息。需要强调的是,在分析源码的时候,我们要排除干扰,抓住主要矛盾,集中精力在核心逻辑。
ps:能力有限,希望看到本文的小伙伴,多多批评指正。
···
[weakSelf.11bgImagemas_remakeConstraints:^(MASConstraintMaker*make) {
make.left.right.bottom.mas_equalTo(0);
make.height.mas_equalTo(imageHeight);
}];
if(image) {
UIColor*bgColor = [ToolscolorWithPicture:imageinPoint:CGPointMake(1,1)];
[weakSelf.upBgViewsetBackgroundColor:bgColor];
[weakSelf.underViewsetBackgroundColor:bgColor];
}
···
cache_t的源码还是比较繁琐的,阅读起来比较费劲,首先我们来精简源码,让我们更容易把握住cache_t结构的主要脉络,而不是迷失在细节中。这可能是我们刚开始阅读源码最容易遇到的障碍。
首先cache_t是一个结构体,那么我么排除static静态变量,他们是不存在结构体中的。接下来我们排除struct中定义的方法。我们得到cache_t有效成员结构如下:
struct cache_t {
explicit_atomic<uintptr_t> _bucketsAndMaybeMask;// 8
union{
struct{
explicit_atomic<mask_t> _maybeMask;// 4
#if __LP64__
uint16_t _flags; // 2
#endif
uint16_t _occupied;// 2
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache;// 8
};
}
看起来是不是还有点啰嗦,继续简化。
1.1、explicit_atomic<>看着就奇奇怪怪,能不能干掉?
1.2、union的preopt_cache_t *和struct能不能干掉一个,干掉哪个?
1.3、__LP64 64位系统,现在基本都是64位的保留,最终得到cache_t如下:
struct cache_t {
uintptr_t _bucketsAndMaybeMask;//8
_bucketsAndMaybeMask is a buckets_t pointer
mask_t _maybeMask;//4 _maybeMask is the buckets mask
uint16_t _flags;//2 标记位,缓存一些方法信息
uint16_t _occupied;//2 buckets内元素的个数
}
把cache_t扒的干干净净,再看,是不是很简洁很爽。😁,接下来我们来研究一下cache_t的每个成员,他们的作用以及他们是怎么工作的。
二、_bucketsAndMaybeMask
关于_bucketsAndMaybeMask我们看到有俩中注释
继续完善我们的cache_t成员结构
struct cache_t {
buckets_t * _bucketsAndMaybeMask;//8
mask_t _maybeMask;//4
uint16_t _flags;//2
uint16_t _occupied;//2
}
2.1、buckets_t的数据结构
按照我们简化代码的思路继续:
struct bucket_t{
uintptr_t _imp;
SEL _sel;
}
现在是不是一目了然。
2.2、_bucketsAndMaybeMask是怎么工作的呢,他为什么能提高方法的查询效率呢?
增删改查,我们去找一下的查找方法
虽然我们对汇编代码不太熟悉,但是看注释我们很显然看到index是通过sel计算得到,然后meth = cache->buckets[index],看到这里,对哈希表熟悉的同学是不是明白了cache_t查找方法更快?对,cache->buckets是一个哈希表,方法存储的位置是通过sel计算的得到的,时间复杂度是O(1),典型的空间换时间。
我们继续看一下insert方法,方法缓存是如何添加的
看起来代码不少,核心步骤就3件事
1.确保buckets有足够缓存空间
buckets为空,reallocate(oldCapacity,capacity,false)开辟缓存空间
buckets的newOccupied + 1 <= capacity *3/4 空间够用什么也不做
buckets的newOccupied + 1 > capacity *3/4 ,就reallocate(oldCapacity,capacity*2,false)二倍扩容
2.cache_hash(sel)计算插入位置
3.循环查找插入位置
b[i]为空插入 return
b[i].sel() == sel return
其他情况 i + 1
三、_maybeMask/_occupied/_flags
_maybeMask 记录buckets的容量
_occupied 记录buckets已存方法个数
_flag这个比较特殊
苹果对_flags的使用真是勤俭持家的代表,每一位都有意义
四、跑一跑代码
五、cache_t总结
本篇我们从哪些可以简化、为什么可以简化开始,逐步简化源码搞清楚了cache_t的主要脉络。其中最重要的是bucket_t * _bucketsAndMaybeMask这个指针,它指向缓存方法的哈希数组,存储bucket_t 数据成员。当然_bucketsAndMaybeMask在不同的编译分支(#if #elif)中,也可以通过掩码存取更多信息。需要强调的是,在分析源码的时候,我们要排除干扰,抓住主要矛盾,集中精力在核心逻辑。
ps:能力有限,希望看到本文的小伙伴,多多批评指正。