NSObject 底层原理分析(二) -- SideTable
上一篇NSObject 底层原理分析(一)中我们讨论了关于[[NSObject alloc] init],[NSObject new]以及[NSObject dealloc]的执行流程。其中,我们有提到释放NSObject中会判断当前的isa是否经过SideTable优化。因此,这篇文章,我们将研究SideTable到底是什么?!
SlideTable 结构
struct SideTable {
spinlock_t slock;
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);
};
SideTable结构体构成:
- spinlock_t slock 自旋锁slock;
- RefcountMap refcnts 强引用相关;
- weak_table_t weak_table 弱引用相关;
Side Table初始化与析构函数
SideTable() {
memset(&weak_table, 0, sizeof(weak_table));
}
~SideTable() {
_objc_fatal("Do not delete SideTable.");
}
Side Table 提供方法
void lock() { slock.lock(); } // 调用自旋锁方法
void unlock() { slock.unlock(); } // 解除自旋锁方法
void forceReset() { slock.forceReset(); } // 锁重制方法
template<HaveOld, HaveNew>
static void lockTwo(SideTable *lock1, SideTable *lock2);
template<HaveOld, HaveNew>
static void unlockTwo(SideTable *lock1, SideTable *lock2);
自旋锁原理
它是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。
SlideTable 调用
SlideTable 引用计数+1
当对象调用[obj retain]时候,实际上会出发Objc4中objc_object::rootRetain调用,表明当前引用计数+1,我们来看一下在这个方法中到底做了什么操作。
if (isTaggedPointer()) return (id)this;
首先,判断当前obj对象是否是Tagged Pointer标记过的对象(上一篇我们知道苹果针对64bit设备提供了直接存储小对象的Tagged Pointer技术),当前对象被Tagged Pointer Mask过后则直接放回当前对象即可。
bool sideTableLocked = false;
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
do{
} while(slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
没有被Tagged Pointer标记过的对象则会继续执行上面这些代码,初始化Side Table部分信息。StoreExclusive()函数封装了&isa.bits 和 oldisa.bits 进行原子比较字节逐位相等的话,则把 newisa.bits 复制这一部分逻辑。
return __c11_atomic_compare_exchange_weak((_Atomic(uintptr_t) *)dst, &oldvalue, value, __ATOMIC_RELAXED, __ATOMIC_RELAXED);
do {
transcribeToSideTable = false;
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (rawISA()->isMetaClass()) return (id)this;
if (!tryRetain && sideTableLocked) sidetable_unlock();
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain();
}
// don't check newisa.fast_rr; we already called any RR overrides
if (slowpath(tryRetain && newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
return nil;
}
uintptr_t carry;
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
if (slowpath(carry)) {
// newisa.extra_rc++ overflowed
if (!handleOverflow) {
ClearExclusive(&isa.bits);
return rootRetain_overflow(tryRetain);
}
// Leave half of the retain counts inline and
// prepare to copy the other half to the side table.
if (!tryRetain && !sideTableLocked) sidetable_lock();
sideTableLocked = true;
transcribeToSideTable = true;
newisa.extra_rc = RC_HALF;
newisa.has_sidetable_rc = true;
}
} while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
do-while循环中执行的LoadExclusive(&isa.bits)读取isa的bits信息,并赋值给了oldisa。判断当前newisa没有被Tagged Pointer标记过,则执行以下代码。
ClearExclusive(&isa.bits);
if (rawISA()->isMetaClass()) return (id)this;
if (!tryRetain && sideTableLocked) sidetable_unlock();
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain();
- 当前对象是元对象则直接返回obj;
- 当前对象没有tryRetain引用并且被sidetable的自旋锁锁住则执行自旋锁解锁操作;
- 当前对象tryRetain则sidetable进行引用;
这里我们重点关注Side Table Retain做的操作:
id
objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISA
ASSERT(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this];
table.lock();
size_t& refcntStorage = table.refcnts[this];
if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
refcntStorage += SIDE_TABLE_RC_ONE;
}
table.unlock();
return (id)this;
}
- Slide Table强引用当前obj对象,执行引用计数+1操作refcntStorage += SIDE_TABLE_RC_ONE;
if (slowpath(transcribeToSideTable)) {
// Copy the other half of the retain counts to the side table.
sidetable_addExtraRC_nolock(RC_HALF);
}
bool
objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
{
ASSERT(isa.nonpointer);
SideTable& table = SideTables()[this];
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;
}
}
- 根据this,从Side Tables中取出Side Table;
- 获取 SideTable 的 refcnts,这个成员变量是一个 Map;
- 存储旧的引用计数器;
- 进行 add 计算,并记录是否有溢出;
- 根据是否溢出计算并记录结果,最后返回;
SideTable 引用技术-1
当我们调用[obj release]操作时候,则会调用以下方法
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc = true, bool handleUnderflow = false)
rootRelease核心方法调用了sidetable_release()方法,与retain调用相同,进行逐个字节判断进行循环。
uintptr_t
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
ASSERT(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this];
bool do_dealloc = false;
table.lock();
auto it = table.refcnts.try_emplace(this, SIDE_TABLE_DEALLOCATING);
auto &refcnt = it.first->second;
if (it.second) {
do_dealloc = true;
} else if (refcnt < SIDE_TABLE_DEALLOCATING) {
// SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
do_dealloc = true;
refcnt |= SIDE_TABLE_DEALLOCATING;
} else if (! (refcnt & SIDE_TABLE_RC_PINNED)) {
refcnt -= SIDE_TABLE_RC_ONE;
}
table.unlock();
if (do_dealloc && performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
return do_dealloc;
}
sidetable_release()判断释放条件,当引用计数<SIDE_TABLE_DEALLOCATING时,进行释放dealloc操作。否则,引用计数-1。
define SIDE_TABLE_DEALLOCATING (1UL<<1) // MSB-ward of weak bit
最终,会触发调用dealloc方法。