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 介入,流程就回变的复杂还要上锁解锁,影响效率。虽然一般开发过程中很难遇到这种情况,但是理解了这些后,对一些问题一些现象就能更清楚的看到本质。

参考

对象本质探究与isa

Last updated