在runtime中,有四个数据结构非常重要,分别是SideTables,SideTable,weak_table_t和weak_entry_t。它们和对象的引用计数,以及weak引用相关。
关系
先说一下这四个数据结构的关系。 在runtime内存空间中,SideTables
是一个64个元素长度8个元素长度 的hash数组,里面存储了SideTable
。SideTables
的hash键值就是一个对象obj
的address
。
因此可以说,一个obj
,对应了一个SideTable
。但是一个SideTable
,会对应多个obj
。因为SideTable
的数量只有64个,所以会有很多obj
共用同一个SideTable
。
而在一个SideTable
中,又有两个成员,分别是
RefcountMap refcnts; // 对象引用计数相关 map
weak_table_t weak_table; // 对象弱引用相关 table
其中,refcents
是一个hash map,其key是obj的地址,而value,则是obj对象的引用计数。
而weak_table
则存储了弱引用obj的指针的地址,其本质是一个以obj地址为key,弱引用obj的指针的地址作为value的hash表。hash表的节点类型是weak_entry_t
。
这四个数据结构的关系如下图:
SideTables
先来说一下最外层的SideTables
。SideTables
可以理解为一个全局的hash数组,里面存储了SideTable
类型的数据,其长度为64
。
SideTabls
可以通过全局的静态函数获取:
static StripedMap<SideTable>& SideTables() {
return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
可以看到,SideTabls
实质类型为模板类型StripedMap
。StripedMap
直译过来是“有条纹的Map”,不知道为什么叫做这个鸟名字。
StripedMap
我们继续来看StripedMap
模板的定义:
// StripedMap<T> is a map of void* -> T, sized appropriately
// for cache-friendly lock striping.
// For example, this may be used as StripedMap<spinlock_t>
// or as StripedMap<SomeStruct> where SomeStruct stores a spin lock.
template<typename T>
class StripedMap {
enum { CacheLineSize = 64 };
#if TARGET_OS_EMBEDDED
enum { StripeCount = 8 };
#else
enum { StripeCount = 64 }; // iOS 设备的StripeCount = 64
#endif
struct PaddedT {
T value alignas(CacheLineSize); // T value 64字节对齐
};
PaddedT array[StripeCount]; // 所有PaddedT struct 类型数据被存储在array数组中。iOS 设备 StripeCount == 64
static unsigned int indexForPointer(const void *p) { // 该方法以void *作为key 来获取void *对应在StripedMap 中的位置
uintptr_t addr = reinterpret_cast<uintptr_t>(p);
return ((addr >> 4) ^ (addr >> 9)) % StripeCount; // % StripeCount 防止index越界
}
public:
// 取值方法 [p],
T& operator[] (const void *p) {
return array[indexForPointer(p)].value;
}
const T& operator[] (const void *p) const {
return const_cast<StripedMap<T>>(this)[p];
}
// Shortcuts for StripedMaps of locks.
void lockAll() {
for (unsigned int i = 0; i < StripeCount; i++) {
array[i].value.lock();
}
}
void unlockAll() {
for (unsigned int i = 0; i < StripeCount; i++) {
array[i].value.unlock();
}
}
void forceResetAll() {
for (unsigned int i = 0; i < StripeCount; i++) {
array[i].value.forceReset();
}
}
void defineLockOrder() {
for (unsigned int i = 1; i < StripeCount; i++) {
lockdebug_lock_precedes_lock(&array[i-1].value, &array[i].value);
}
}
void precedeLock(const void *newlock) {
// assumes defineLockOrder is also called
lockdebug_lock_precedes_lock(&array[StripeCount-1].value, newlock);
}
void succeedLock(const void *oldlock) {
// assumes defineLockOrder is also called
lockdebug_lock_precedes_lock(oldlock, &array[0].value);
}
const void *getLock(int i) {
if (i < StripeCount) return &array[i].value;
else return nil;
}
};
通过开头的英文注释,
// StripedMap is a map of void* -> T, sized appropriately
可以知道, StripedMap
是一个以void *
为hash key, T
为vaule的hash 表。
hash定位的算法如下:
static unsigned int indexForPointer(const void *p) { // 该方法以void *作为key 来获取void *对应在StripedMap 中的位置
uintptr_t addr = reinterpret_cast<uintptr_t>(p);
return ((addr >> 4) ^ (addr >> 9)) % StripeCount; // % StripeCount 防止index越界
}
把地址指针右移4位异或地址指针右移9位,为什么这么做,也不用关心。我们只要关心重点是最后的值要取余StripeCount
,来防止index越界就好。
StripedMap
的所有T
类型数据都被封装到PaddedT
中:
struct PaddedT {
T value alignas(CacheLineSize); // T value 64字节对齐
};
之所以再次封装到PaddedT
(有填充的T)中,是为了字节对齐,估计是存取hash值时的效率考虑。
接下来,这些PaddedT
被放到数组array
中:
PaddedT array[StripeCount]; // 所有PaddedT struct 类型数据被存储在array数组中。iOS 设备 StripeCount == 64
然后,苹果为array数组写了一些公共的存取数据的方法,主要是调用indexForPointer
方法,使得外部传入的对象地址指针
直接hash到对应的array节点:
// 取值方法 [p],
T& operator[] (const void *p) {
return array[indexForPointer(p)].value;
}
const T& operator[] (const void *p) const {
return const_cast<StripedMap<T>>(this)[p];
}
接下来是一堆锁的操作,由于SideTabls
是一个全局的hash表,因此当然必须要带锁访问。StripedMap
提供了一些便捷的锁操作方