839 字
4 分钟
Retain&Release
前言
到现在我们接触到了三种指针: TaggedPointer 、 Nonpointer isa 和 纯isa 。
经过上文对 TaggedPointer 的探索,我们接触到了 Reatin 和 Release ,发现他们对 TaggedPointer 有特殊处理的。本文将更加深入的对 Reatin 和 Release 进行研究。
一、 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不处理,直接 returnNonpointer isaBITFIELD和SideTable结合使用- 当
BITFIELD的extra_rc容量足够的时候持续 ++ - 当
BITFIELD的extra_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不处理,直接 returnNonpointer isaBITFIELD和SideTable结合使用- 当
BITFIELD的extra_rc容量未见底的时候持续 — - 当
BITFIELD的extra_rc容量见底的时候- 检查标志位
has_sidetable_rc- 使用了
SideTable- 将
SideTable中的引用计数转移回BITFIELD的extra_rc进行处理
- 将
- 未使用
SideTable- 需要销毁
- 使用了
- 检查标志位
- 当
纯isa- 通过
SideTable管理
- 通过
三、 总结
通过对 Retain 和 Release 源码的探索,发现使用 TaggedPointer 和 Nonpointer isa 在内存管理的效率上会更高。一旦 SideTable 介入,流程就回变的复杂还要上锁解锁,影响效率。虽然一般开发过程中很难遇到这种情况,但是理解了这些后,对一些问题一些现象就能更清楚的看到本质。