Links

02.Retain&Release

前言

到现在我们接触到了三种指针: TaggedPointerNonpointer isa纯isa
经过上文TaggedPointer 的探索,我们接触到了 ReatinRelease ,发现他们对 TaggedPointer 有特殊处理的。本文将更加深入的对 ReatinRelease 进行研究。

一、 Retain

objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
{
// 不处理 TaggedPointer 直接 return
if (slowpath(isTaggedPointer())) return (id)this;
...
do {
transcribeToSideTable = false;
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
// 非 nonpointer 通过 sidetable 处理
ClearExclusive(&isa.bits);
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain(sideTableLocked);
}
...
uintptr_t carry; // 标志 extra_rc 容量是否还能够承受
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
if (slowpath(carry)) {
// 超出 extra_rc 容量
// newisa.extra_rc++ overflowed
if (variant != RRVariant::Full) {
ClearExclusive(&isa.bits);
return rootRetain_overflow(tryRetain);
}
// 保留一般的引用计数,并将另一半拷贝到 side table
// 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)));
if (variant == RRVariant::Full) {
if (slowpath(transcribeToSideTable)) {
// Copy the other half of the retain counts to the side table.
sidetable_addExtraRC_nolock(RC_HALF);
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
} else {
ASSERT(!transcribeToSideTable);
ASSERT(!sideTableLocked);
}
return (id)this;
}

小结

  • TaggedPointer 不处理,直接 return
  • Nonpointer isa
    • BITFIELDSideTable 结合使用
      • BITFIELDextra_rc 容量足够的时候持续 ++
      • BITFIELDextra_rc 容量不够的时候
        • extra_rc 减半,拷贝一半到 SideTable 进行管理
        • 同时标志位 has_sidetable_rc 改变
  • 纯isa
    • 通过 SideTable 管理

二、 Release

objc_object::rootRelease(bool performDealloc, objc_object::RRVariant variant)
{
// 不处理 TaggedPointer 直接 return
if (slowpath(isTaggedPointer())) return false;
...
retry:
do {
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
// 非 nonpointer 通过 sidetable 处理
ClearExclusive(&isa.bits);
return sidetable_release(sideTableLocked, performDealloc);
}
...
// don't check newisa.fast_rr; we already called any RR overrides
uintptr_t carry;
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
if (slowpath(carry)) {
// don't ClearExclusive()
// extra_rc 见底 跳转执行 underflow
goto underflow;
}
} while (slowpath(!StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits)));
if (slowpath(newisa.isDeallocating()))
goto deallocate;
...
return false;
underflow:
// extra_rc 见底后,从 SideTable 拿回引用计数,或者销毁
// newisa.extra_rc-- underflowed: borrow from side table or deallocate
// abandon newisa to undo the decrement
newisa = oldisa;
if (slowpath(newisa.has_sidetable_rc)) {
// 如果有使用 SideTable
if (variant != RRVariant::Full) {
ClearExclusive(&isa.bits);
return rootRelease_underflow(performDealloc);
}
// Transfer retain count from side table to inline storage.
if (!sideTableLocked) {
ClearExclusive(&isa.bits);
sidetable_lock();
sideTableLocked = true;
// Need to start over to avoid a race against
// the nonpointer -> raw pointer transition.
oldisa = LoadExclusive(&isa.bits);
goto retry;
}
// 从 SideTable 移除引用计数
// Try to remove some retain counts from the side table.
auto borrow = sidetable_subExtraRC_nolock(RC_HALF);
bool emptySideTable = borrow.remaining == 0; // we'll clear the side table if no refcounts remain there
if (borrow.borrowed > 0) {
// Side table retain count decreased.
// Try to add them to the inline count.
bool didTransitionToDeallocating = false;
// 修改 extra_rc 及标志位
newisa.extra_rc = borrow.borrowed - 1; // redo the original decrement too
newisa.has_sidetable_rc = !emptySideTable;
...
}
else {
// Side table is empty after all. Fall-through to the dealloc path.
}
}
deallocate:
// Really deallocate.
// 销毁
...
if (performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
return true;
}

小结

  • TaggedPointer 不处理,直接 return
  • Nonpointer isa
    • BITFIELDSideTable 结合使用
      • BITFIELDextra_rc 容量未见底的时候持续 --
      • BITFIELDextra_rc 容量见底的时候
        • 检查标志位 has_sidetable_rc
          • 使用了 SideTable
            • SideTable 中的引用计数转移回 BITFIELDextra_rc 进行处理
          • 未使用 SideTable
            • 需要销毁
  • 纯isa
    • 通过 SideTable 管理

三、 总结

通过对 Retain 和 Release 源码的探索,发现使用 TaggedPointerNonpointer isa 在内存管理的效率上会更高。一旦 SideTable 介入,流程就回变的复杂还要上锁解锁,影响效率。虽然一般开发过程中很难遇到这种情况,但是理解了这些后,对一些问题一些现象就能更清楚的看到本质。

参考