开启生长之旅!这是我参加「日新计划 12 月更文应战」的第4天,点击检查活动概况
一、sidetable
直接检查objc源码找到sidetable_addExtraRC_nolock
:
bool
objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
{
ASSERT(isa.nonpointer);
SideTable& table = SideTables()[this];//获取目标的SideTable
size_t& refcntStorage = table.refcnts[this];
size_t oldRefcnt = refcntStorage;
// isa-side bits should not be set here
ASSERT((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
ASSERT((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);
if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true;
uintptr_t carry;
size_t newRefcnt =
addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);
if (carry) {
refcntStorage =
SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);
return true;
}
else {
refcntStorage = newRefcnt;
return false;
}
}
咱们来看下SideTable
的数据结构:
struct SideTable {
spinlock_t slock;//确保线程安全 os_unfair_lock mLock; 是互斥锁
RefcountMap refcnts;//存储目标的引证计数
weak_table_t weak_table;//弱引证表,加锁 解锁 低效
SideTable() {
memset(&weak_table, 0, sizeof(weak_table));
}
~SideTable() {
_objc_fatal("Do not delete SideTable.");
}
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()
的源码:
static StripedMap<SideTable>& SideTables() { //创立获取StripedMap list 真机8个
return SideTablesMap.get();
}
在看下StripedMap:
StripedMap
是用做:缓存带有spinlock_t
锁的才能的类或者是结构体。这个map的个数是固定的,模拟器64个,真机是8个。
我们可以思考一下,在整个项目中只初始化了一个SideTable和所有目标的weak_table_t表。可是这样的效率会很低,由于有spinlock_t加锁,解锁而形成低效的问题
。可是假如每个目标都创立SideTable和weak_table_t表,效率是高了可是内存占用过高
。
apple是怎么解决呢,用了什么方案呢?
来咱们直接看StripedMap
部分源码:
PaddedT array[StripeCount];
//中心算法,均匀分配到真机8张表中。
static unsigned int indexForPointer(const void *p) {
uintptr_t addr = reinterpret_cast<uintptr_t>(p);
return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
}
方法indexForPointer
经过目标的地址,来确定是在array中的第几个元素,进而拿到哈希值。
而要害的地方在于,这个类对[]
运算符进行了重载,前面取出对应的SideTable
用的方法是SideTables()[this]
,它的翻译过来的实际操作是:SideTables().array[indexForPointer(this)].value
,便是先经过indexForPointer
计算出位置,在经过array[index]取出值,便是对应的SideTable
。
弱引证表 weak_table_t weak_table
直接进入weak_table_t
源码检查:
struct weak_table_t {
weak_entry_t *weak_entries;
size_t num_entries;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
在进入weak_entry_t
源码检查:
struct weak_entry_t {
DisguisedPtr<objc_object> referent;
union {
struct {
weak_referrer_t *referrers;
uintptr_t out_of_line_ness : 2;
uintptr_t num_refs : PTR_MINUS_2;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
struct {
// out_of_line_ness field is low bits of inline_referrers[1]
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
bool out_of_line() {
return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
}
weak_entry_t& operator=(const weak_entry_t& other) {
memcpy(this, &other, sizeof(other));
return *this;
}
weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
: referent(newReferent)
{
inline_referrers[0] = newReferrer;
for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
inline_referrers[i] = nil;
}
}
};
weak_entry_t存储着某个目标的弱引证信息,又是一个结构体。跟weak_table_t相似,里边存储一个hash表weak_referrer_t
,弱引证该”目标的指针”的指针。经过weak_referrer_t
的操作,可以使该目标的弱引证指针在目标开释后,置为nil。
DisguisedPtr<objc_object> referent :弱引证目标指针摘要。其实可以理解为弱引证目标的指针,只不过这里运用了摘要的方式存储。(所谓摘要,其实是把地址取负)。
weak_referrer_t
:这是一个共用体,分动态数组和固定长度数组两种情况,
out_of_line
:bool类型区别是weak_referrer_t中数组类型
weak_entry_t& operator=(const weak_entry_t& other)
:赋值
weak_entry_t(objc_object *newReferent, objc_object **newReferrer) 构造
在检查weak_referrer_t *referrers;
的源码完成:
typedef DisguisedPtr<objc_object *> weak_referrer_t;
weak_referrer_t
以指针摘要的方式,存储弱引证指针的指针。weak_referrer_t数组这里设置了两种模式,究竟动态数组涉及到更多的操作,在小数据量的情况下,运用定长数组效率更高。
总结
iOS的一个项目:会全局保护一个SideTables
,SideTables里边包含多个sidetable
(真机情况下是8个)。
sidetable
中有:spinlock_t(确保线程安全),RefcountMap(存储目标的引证计数),weak_table_t(弱引证表,加锁 解锁 低效) 三个中心属性。
weak_table_t
:中保存着的一个sidetable中所有weak_entries表,从weak_entries中经过目标查找着某个目标对应的弱引证信息weak_entry_t,weak_entry_t中保存着弱引证该目标的 指针地址的hash数组。