1. retain的实现原理
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
// TaggedPointer 不retain直接返回
if (isTaggedPointer()) return (id)this;
bool sideTableLocked = false;
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
do {
// 默认不抄写到SideTable,isa指针5w+足以
transcribeToSideTable = false;
// 原子获取isa
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
// 非共用体isa, 一定会有一个结果,不会do-while的
if (slowpath(!newisa.nonpointer)) {
// 原子清空对象的isa.bits
ClearExclusive(&isa.bits);
// isa 指向原类,说明是类对象,直接返回
if (rawISA()->isMetaClass())
return (id)this;
// retain 必须成功返回true,那么如果当前线程锁住了sideTable对象,需要解锁。
if (!tryRetain && sideTableLocked)
sidetable_unlock();
if (tryRetain)
// try retain 会返回bool值以判定是否retain成功,由调用者自行do-while重试。
return sidetable_tryRetain() ? (id)this : nil;
else
// retain 必须成功返回true,所以sidetable_retain会加lock。
return sidetable_retain();
}
// don't check newisa.fast_rr; we already called any RR overrides
if (slowpath(tryRetain && newisa.deallocating)) {
// 正在释放,原子清空isa.bits
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked)
// 释放sidetable资源
sidetable_unlock();
return nil;
}
// 把new isa改一改,CAS操作失败继续do-while,直至成功
uintptr_t carry;
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
// isa extra_rc值超过2^19
if (slowpath(carry)) {
// 不处理越界,什么鬼?
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加锁。如果等isa改完后再锁sidetable就完了。在isa和sidetable需要同时调整时,两件事是一个完成的事务。
sidetable_lock();
// 标记下extra_rc满了extra_rc设置一半
sideTableLocked = true;
transcribeToSideTable = true;
newisa.extra_rc = RC_HALF;
newisa.has_sidetable_rc = true;
}
} while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
if (slowpath(transcribeToSideTable)) {
// 拷贝另一半到side table. 那么为什么一人一半呢 ?
// 因为,isa操作快啊
// 那么为什么不全拷贝过去?
// 因为还要release,ISA减到0又要拷贝部分过来不是吗,所以是兄弟就AA吧。
sidetable_addExtraRC_nolock(RC_HALF);
}
// 所有操作完成,解锁
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
return (id)this;
}
-------------
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;
}
- 注意点:
- 修改ISA通过自旋CAS完成,在这种场景下,冲突的可能性小,性能高。
- 修改SideTables时通过加锁完成,所以性能会差很多(weak引用同理)
- 为了权衡isa 2^19的空间限制问题和性能问题,Apple采用了HALF_RC平均分配(还有release,需要考虑isa extra_rc减到0需要拷贝回来的问题)。
- extra_rc 与 sideTable需要同时,需要注意事务的原子性和隔离性,提前加锁。
release和retain的原理和注意点相似,倒过来就好了。