SideTable主要存放了OC对象的引用计数和弱引用相关信息。定义如
下:
struct SideTable {
spinlock_t slock; // 自旋锁,防止多线程访问冲突
RefcountMap refcnts; // 对象引用计数map
weak_table_t weak_table; // 对象弱引用map
SideTable() {
memset(&weak_table, 0, sizeof(weak_table));
}
~SideTable() {
_objc_fatal("Do not delete SideTable.");
}
// 锁操作 符合StripedMap对T的定义
void lock() { slock.lock(); }
void unlock() { slock.unlock(); }
void forceReset() { slock.forceReset(); }
// Address-ordered lock discipline for a pair of side tables.
template<HaveOld, HaveNew>
static void lockTwo(SideTable *lock1, SideTable *lock2);
template<HaveOld, HaveNew>
static void unlockTwo(SideTable *lock1, SideTable *lock2);
};
SideTable的定义很清晰有三个成员:
●spinlock_t slock : 自旋锁,用于上锁/解锁 SideTable。
●RefcountMap refcnts :以DisguisedPtr<objc_object>为key的hash表,用来存储OC对象的引用计数(仅在未开启isa优化 或 在isa优化情况下isa_t的引用计数溢出时才会用到)。
●weak_table_t weak_table : 存储对象弱引用指针的hash表。是OC weak功能实现的核心数据结构。
除了三个成员外,苹果为SideTable还写了构造和析构函数:
// 构造函数
SideTable() {
memset(&weak_table, 0, sizeof(weak_table));
}
//析构函数(看看函数体,苹果设计的SideTable其实不希望被析构,不然会引起fatal 错误)
~SideTable() {
_objc_fatal("Do not delete SideTable.");
}
通过析构函数可以知道,SideTable是不能被析构的。
最后是一堆锁的操作,用于多线程访问SideTable, 同时,也符合我们上面提到的StripedMap中关于value的lock接口定义:
// 锁操作 符合StripedMap对T的定义
void lock() { slock.lock(); }
void unlock() { slock.unlock(); }
void forceReset() { slock.forceReset(); }
// Address-ordered lock discipline for a pair of side tables.
template<HaveOld, HaveNew>
static void lockTwo(SideTable *lock1, SideTable *lock2);
template<HaveOld, HaveNew>
static void unlockTwo(SideTable *lock1, SideTable *lock2);
顺便了解下SideTables
SideTables是一个64个元素长度的hash数组,里面存储了SideTable。SideTables的hash键值就是一个对象obj的address。
因此可以说,一个obj,对应了一个SideTable。但是一个SideTable,会对应多个obj。因为SideTable的数量只有64个,所以会有很多obj共用同一个SideTable。
image.png
先来说一下最外层的SideTables。SideTables可以理解为一个全局的hash数组,里面存储了SideTable类型的数据,其长度为64。
SideTabls可以通过全局的静态函数获取:
static StripedMap<SideTable>& SideTables() {
return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
可以看到,SideTabls 实质类型为模板类型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 是一个以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提供了一些便捷的锁操作方法:
// 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锁操作,最终是调用的array[i].value的相关操作。因此,对于模板的抽象数据T类型,必须具备相关的lock操作接口。
因此,要用StripedMap作为模板hash表,对于T类型还是有所要求的。而在SideTables中,T即为SideTable类型,我们稍后会看到SideTable是如何符合StripedMap的数据类型要求的。
网友评论