在runtime内存空间中,SideTables是一个hash数组,里面存储了SideTable。SideTables的hash键值就是一个对象obj的address。
因此可以说,一个obj,对应了一个SideTable。但是一个SideTable,会对应多个obj。因为SideTable的数量有限,所以会有很多obj共用同一个SideTable。
<
1. 复杂的数据结构,包括了引用计数表和弱引用表 通过 SideTables()结构来实现的,SideTables()结构下,有很多 SideTable 的数据结构。 2. 而 sideTable 当中包含了自旋锁,引用计数表,弱引用表。 SideTables()实际上是一个哈希表,通过对象的地址来计算该对象的引用计数在哪个 sideTable 中。 >
我们来看一下SideTable的结构
struct SideTable {
spinlock_t slock; // 自旋锁
RefcountMap refcnts; //引用计数的Map表 key-value
weak_table_t weak_table; //弱引用表
来抛出一个问题,为什么不直接用一张SideTable,而是用SideTables去管理多个SideTable? SideTable里有一个自旋锁,如果把所有的类都放在同一个SideTable,有任何一个类有改动都会对整个table做操作,并且在操作一个类的同时,操作别的类会被锁住等待,这样会导致操作效率和查询效率都很低。而有多个SideTable的话,操作的都是单个Table,并不会影响其他的table,这就是分离锁。
继续SideTables,来看一下散列表的数据结构(数组+链表),举个例子,我们需要把小于100的放到第1个Table,大于900的放到第6个Table:
如何从sideTables里找到特定的sideTable呢,这就用到了散列函数。查看源码,runtime是通过这么一个函数来获取到相应的sideTable:
table = &SideTables()[obj];
点进去:
static StripedMap<SideTable>& SideTables() {
return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
复制代码
如果看不懂没关系,来看 **StripedMap **的定义:
template<typename T>
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
enum { StripeCount = 8 }; // iPhone时这个值为8
#else
enum { StripeCount = 64 }; //否则为64
#endif
struct PaddedT {
T value alignas(CacheLineSize);
};
PaddedT array[StripeCount];
static unsigned int indexForPointer(const void *p) {
//这里是做类型转换
uintptr_t addr = reinterpret_cast<uintptr_t>(p);
//这就是哈希算法了
return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
}
public:
T& operator[] (const void *p) {
//返回sideTable
return array[indexForPointer(p)].value;
}
复制代码
可以看到,在对StripeCount取余后,所得到的值根据机器不同,会在0-7或者0-63之间,这就是通过哈希函数来获取到了sideTable的下标,然后再根据value取到所需的sideTable。
我们来验证一下: 编译objc源码,打上断点看一下具体调用:
执行table = &SideTables()[obj];之后,执行到了array[indexForPointer(p)].value;,然后进行哈希算法获取到下标,再返回所需的sideTable
作者:聪莞
链接:juejin.cn/post/684490…
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。