Skip to content

Reference Count

ShenYj edited this page Jun 1, 2023 · 3 revisions

Reference Count

iOS 中 Object-CSwift 都是引用计数的方式来管理内存,在实现上存在一些区别

从存储结构上对比

暂时不考虑 weak 引用和 TaggedPointer 小对象这种特殊场景情况

目前网上看到的资料基本出自小码哥李明杰和逻辑教育的 OC 底层课程,这里我将结合 objc-781 和暂时最新的 objc-818.2 进行对比学习、总结

Object-C

  1. Object-C 经过优化后的 isa,首先会直接存储在 isaextra_rc 这块 19 个二进制位空间中 (存储的值为 引用计数-1

  2. 由于存储空间有限,就有可能会超出,因此在 isa 中额外有一个标记位 has_sidetable_rc

  3. has_sidetable_rc1,时,引用计数就会被存储在一个叫 SideTable 的类的属性中

    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);
    };

    这里的 slock 不要看类型就认为是自旋锁,通过 818.2 源码可见,是互斥锁的别名 typedef mutex_t spinlock_t;
    RefcountMap refcnts; 就是用来存储引用计数的,是一个散列表的结构

  4. 非优化后的 isa, 直接存储在 SideTable

    关于 isa 的结构布局,参考笔记 isa


Object-C 获取引用计数的过程

在调用 retainCount 方法时,内部会调用到 _objc_rootRetainCount 函数

- (NSUInteger)retainCount {
    return _objc_rootRetainCount(self);
}

紧接着调用到 rootRetainCount 函数

uintptr_t
_objc_rootRetainCount(id obj)
{
    ASSERT(obj);

    return obj->rootRetainCount();
}

这里会通过 SUPPORT_NONPOINTER_ISA 标记判断是否是优化后的 isa 执行不同的函数,由于目前是优化后的 isa, 所以 rootRetainCount 函数的具体实现是

// 781 源码
inline uintptr_t 
objc_object::rootRetainCount()
{
    if (isTaggedPointer()) return (uintptr_t)this;

    sidetable_lock();
    isa_t bits = LoadExclusive(&isa.bits);
    ClearExclusive(&isa.bits);
    if (bits.nonpointer) {
        uintptr_t rc = 1 + bits.extra_rc;
        if (bits.has_sidetable_rc) {
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }

    sidetable_unlock();
    return sidetable_retainCount();
}

// 818.2 源码
inline uintptr_t 
objc_object::rootRetainCount()
{
    if (isTaggedPointer()) return (uintptr_t)this;

    sidetable_lock();
    // 这行代码与上面的处理没有区别, LoadExclusive(&isa.bits) 函数也是直接调用了 _c11_atomic_load((_Atomic uintptr_t *)&isa.bits, __ATOMIC_RELAXED) 返回 
    isa_t bits = __c11_atomic_load((_Atomic uintptr_t *)&isa.bits, __ATOMIC_RELAXED);
    if (bits.nonpointer) {
        uintptr_t rc = bits.extra_rc;
        if (bits.has_sidetable_rc) {
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }

    sidetable_unlock();
    return sidetable_retainCount();
}
// isa 优化前直接从 SideTable 取计数值的方法 (781 和 818.2下无变化)
uintptr_t
objc_object::sidetable_retainCount()
{
    SideTable& table = SideTables()[this];

    size_t refcnt_result = 1;
    
    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it != table.refcnts.end()) {
        // this is valid for SIDE_TABLE_RC_PINNED too
        refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
    }
    table.unlock();
    return refcnt_result;
}

// isa 优化后 从 SideTable 取计数值的方法 (781 和 818.2下无变化)
size_t 
objc_object::sidetable_getExtraRC_nolock()
{
    ASSERT(isa.nonpointer);
    SideTable& table = SideTables()[this];
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it == table.refcnts.end()) return 0;
    else return it->second >> SIDE_TABLE_RC_SHIFT;
}

获取引用计数函数调用过程解读

  1. 判断是 TaggedPointer 的直接返回

    这里也是为什么某些情况下我们打印 NSString 类型的引用计数为 -1 的原因,因为 TaggedPointer 指针足够存储当时的字符串,并不会进行引用计数管理

  2. 取出 isa.bits 64位数据,判断是否是优化后指针类型,如果是优化后的指针,取出 extra_rc 2.1 如果 has_sidetable_rc 标记为为 1,说明有通过 SideTable 额外存储,再取一下 SideTable 的值,加上 extra_rc 中的值,就是最终的引用计数值 2.2 如果 has_sidetable_rc 标记为为 0,就直接返回 extra_rc 里面的计数值就可以了
  3. 如果是非优化后的指针(早期版本),是通过 sidetable_retainCount 函数直接返回计数值

变化

  • 最新的 818.2 源码与 781 源码有所调整,早期文章提到 extra_rc 里面存储的是 引用计数值 - 1,因此在获取计数值的时候,会进行 +1 (uintptr_t rc = 1 + bits.extra_rc;);但从 818.2 源码可见,已经不再 +1

Object-C release 的执行过程

  • 函数的调用过程:

    • 781

      - (void)release -> _objc_rootRelease(self) -> obj->rootRelease() -> rootRelease(false, false)

      最终来到 ALWAYS_INLINE bool objc_object::rootRelease(bool performDealloc, bool handleUnderflow) 函数

      • rootRelease 718 源码

        rootRelease (718 源码)
        ALWAYS_INLINE bool 
        objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
        {
            if (isTaggedPointer()) return false;
        
            bool sideTableLocked = false;
        
            isa_t oldisa;
            isa_t newisa;
        
        retry:
            do {
                oldisa = LoadExclusive(&isa.bits);
                newisa = oldisa;
                if (slowpath(!newisa.nonpointer)) {
                    ClearExclusive(&isa.bits);
                    if (rawISA()->isMetaClass()) return false;
                    if (sideTableLocked) sidetable_unlock();
                    return sidetable_release(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()
                    goto underflow;
                }
            } while (slowpath(!StoreReleaseExclusive(&isa.bits, 
                                                    oldisa.bits, newisa.bits)));
        
            if (slowpath(sideTableLocked)) sidetable_unlock();
            return false;
        
        underflow:
            // newisa.extra_rc-- underflowed: borrow from side table or deallocate
        
            // abandon newisa to undo the decrement
            newisa = oldisa;
        
            if (slowpath(newisa.has_sidetable_rc)) {
                if (!handleUnderflow) {
                    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.
                    goto retry;
                }
        
                // Try to remove some retain counts from the side table.        
                size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);
        
                // To avoid races, has_sidetable_rc must remain set 
                // even if the side table count is now zero.
        
                if (borrowed > 0) {
                    // Side table retain count decreased.
                    // Try to add them to the inline count.
                    newisa.extra_rc = borrowed - 1;  // redo the original decrement too
                    bool stored = StoreReleaseExclusive(&isa.bits, 
                                                        oldisa.bits, newisa.bits);
                    if (!stored) {
                        // Inline update failed. 
                        // Try it again right now. This prevents livelock on LL/SC 
                        // architectures where the side table access itself may have 
                        // dropped the reservation.
                        isa_t oldisa2 = LoadExclusive(&isa.bits);
                        isa_t newisa2 = oldisa2;
                        if (newisa2.nonpointer) {
                            uintptr_t overflow;
                            newisa2.bits = 
                                addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow);
                            if (!overflow) {
                                stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits, 
                                                            newisa2.bits);
                            }
                        }
                    }
        
                    if (!stored) {
                        // Inline update failed.
                        // Put the retains back in the side table.
                        sidetable_addExtraRC_nolock(borrowed);
                        goto retry;
                    }
        
                    // Decrement successful after borrowing from side table.
                    // This decrement cannot be the deallocating decrement - the side 
                    // table lock and has_sidetable_rc bit ensure that if everyone 
                    // else tried to -release while we worked, the last one would block.
                    sidetable_unlock();
                    return false;
                }
                else {
                    // Side table is empty after all. Fall-through to the dealloc path.
                }
            }
        
            // Really deallocate.
        
            if (slowpath(newisa.deallocating)) {
                ClearExclusive(&isa.bits);
                if (sideTableLocked) sidetable_unlock();
                return overrelease_error();
                // does not actually return
            }
            newisa.deallocating = true;
            if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
        
            if (slowpath(sideTableLocked)) sidetable_unlock();
        
            __c11_atomic_thread_fence(__ATOMIC_ACQUIRE);
        
            if (performDealloc) {
                ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
            }
            return true;
        }
    • 818.2

      - (void)release -> _objc_rootRelease(self) -> obj->rootRelease() -> rootRelease(true, RRVariant::Fast)

      最终来到 ALWAYS_INLINE bool objc_object::rootRelease(bool performDealloc, objc_object::RRVariant variant) 函数

      • rootRelease 818.2 源码, 代码比较长,有多处变化

        rootRelease (818.2源码)
        LWAYS_INLINE bool
        objc_object::rootRelease(bool performDealloc, objc_object::RRVariant variant)
        {
            if (slowpath(isTaggedPointer())) return false;
        
            bool sideTableLocked = false;
        
            isa_t newisa, oldisa;
        
            oldisa = LoadExclusive(&isa.bits);
        
            if (variant == RRVariant::FastOrMsgSend) {
                // These checks are only meaningful for objc_release()
                // They are here so that we avoid a re-load of the isa.
                if (slowpath(oldisa.getDecodedClass(false)->hasCustomRR())) {
                    ClearExclusive(&isa.bits);
                    if (oldisa.getDecodedClass(false)->canCallSwiftRR()) {
                        swiftRelease.load(memory_order_relaxed)((id)this);
                        return true;
                    }
                    ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(release));
                    return true;
                }
            }
        
            if (slowpath(!oldisa.nonpointer)) {
                // a Class is a Class forever, so we can perform this check once
                // outside of the CAS loop
                if (oldisa.getDecodedClass(false)->isMetaClass()) {
                    ClearExclusive(&isa.bits);
                    return false;
                }
            }
        
        retry:
            do {
                newisa = oldisa;
                if (slowpath(!newisa.nonpointer)) {
                    ClearExclusive(&isa.bits);
                    return sidetable_release(sideTableLocked, performDealloc);
                }
                if (slowpath(newisa.isDeallocating())) {
                    ClearExclusive(&isa.bits);
                    if (sideTableLocked) {
                        ASSERT(variant == RRVariant::Full);
                        sidetable_unlock();
                    }
                    return false;
                }
        
                // 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()
                    goto underflow;
                }
            } while (slowpath(!StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits)));
        
            if (slowpath(newisa.isDeallocating()))
                goto deallocate;
        
            if (variant == RRVariant::Full) {
                if (slowpath(sideTableLocked)) sidetable_unlock();
            } else {
                ASSERT(!sideTableLocked);
            }
            return false;
        
        underflow:
            // newisa.extra_rc-- underflowed: borrow from side table or deallocate
        
            // abandon newisa to undo the decrement
            newisa = oldisa;
        
            if (slowpath(newisa.has_sidetable_rc)) {
                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;
                }
        
                // 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;
                    newisa.extra_rc = borrow.borrowed - 1;  // redo the original decrement too
                    newisa.has_sidetable_rc = !emptySideTable;
        
                    bool stored = StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits);
        
                    if (!stored && oldisa.nonpointer) {
                        // Inline update failed. 
                        // Try it again right now. This prevents livelock on LL/SC 
                        // architectures where the side table access itself may have 
                        // dropped the reservation.
                        uintptr_t overflow;
                        newisa.bits =
                            addc(oldisa.bits, RC_ONE * (borrow.borrowed-1), 0, &overflow);
                        newisa.has_sidetable_rc = !emptySideTable;
                        if (!overflow) {
                            stored = StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits);
                            if (stored) {
                                didTransitionToDeallocating = newisa.isDeallocating();
                            }
                        }
                    }
        
                    if (!stored) {
                        // Inline update failed.
                        // Put the retains back in the side table.
                        ClearExclusive(&isa.bits);
                        sidetable_addExtraRC_nolock(borrow.borrowed);
                        oldisa = LoadExclusive(&isa.bits);
                        goto retry;
                    }
        
                    // Decrement successful after borrowing from side table.
                    if (emptySideTable)
                        sidetable_clearExtraRC_nolock();
        
                    if (!didTransitionToDeallocating) {
                        if (slowpath(sideTableLocked)) sidetable_unlock();
                        return false;
                    }
                }
                else {
                    // Side table is empty after all. Fall-through to the dealloc path.
                }
            }
        
        deallocate:
            // Really deallocate.
        
            ASSERT(newisa.isDeallocating());
            ASSERT(isa.isDeallocating());
        
            if (slowpath(sideTableLocked)) sidetable_unlock();
        
            __c11_atomic_thread_fence(__ATOMIC_ACQUIRE);
        
            if (performDealloc) {
                ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
            }
            return true;
        }

部分解读 (代码逻辑较多, 818.2 增加了不少代码 )

  1. TaggedPointer 直接返回

  2. 根据缓存的计数值做 -1 操作
    2.1 如果不是优化后的 isa 直接 SideTable 散列表 -1
    2.2 如果是优化后的 isa,则对 extra_rc 中的引用计数值进行 -1

    • 通过 newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc-- 这样代码可以判断出在做 -1 操作
    • extra_rc0后,并且 has_sidetable_rc 标记了有额外的存储计数 size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF); 取出一半 -1 后给 extra_rc
  3. 如果最终 -1 后为 0performDealloc 条件成立,通过 msg_send 执行 dealloc

Object-C retain 的执行过程

  • 函数的调用过程:

    • 781

      -(id) retain -> _objc_rootRetain(self) -> obj->rootRetain() -> rootRetain(false, false)

      最终来到 ALWAYS_INLINE id objc_object::rootRetain(bool tryRetain, bool handleOverflow) 函数

      • rootRetain 718 源码

        rootRetain (718源码)
        ALWAYS_INLINE id 
        objc_object::rootRetain(bool tryRetain, bool handleOverflow)
        {
            if (isTaggedPointer()) return (id)this;
        
            bool sideTableLocked = false;
            bool transcribeToSideTable = false;
        
            isa_t oldisa;
            isa_t newisa;
        
            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)));
        
            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();
            return (id)this;
        }
    • 818.2

      -(id) retain -> _objc_rootRetain(self) -> obj->rootRetain() -> rootRetain(false, RRVariant::Fast)

      最终来到 ALWAYS_INLINE id objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant) 函数

      • rootRetain 818.2 源码

        rootRetain (818.2源码)
        ALWAYS_INLINE id
        objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
        {
            if (slowpath(isTaggedPointer())) return (id)this;
        
            bool sideTableLocked = false;
            bool transcribeToSideTable = false;
        
            isa_t oldisa;
            isa_t newisa;
        
            oldisa = LoadExclusive(&isa.bits);
        
            if (variant == RRVariant::FastOrMsgSend) {
                // These checks are only meaningful for objc_retain()
                // They are here so that we avoid a re-load of the isa.
                if (slowpath(oldisa.getDecodedClass(false)->hasCustomRR())) {
                    ClearExclusive(&isa.bits);
                    if (oldisa.getDecodedClass(false)->canCallSwiftRR()) {
                        return swiftRetain.load(memory_order_relaxed)((id)this);
                    }
                    return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(retain));
                }
            }
        
            if (slowpath(!oldisa.nonpointer)) {
                // a Class is a Class forever, so we can perform this check once
                // outside of the CAS loop
                if (oldisa.getDecodedClass(false)->isMetaClass()) {
                    ClearExclusive(&isa.bits);
                    return (id)this;
                }
            }
        
            do {
                transcribeToSideTable = false;
                newisa = oldisa;
                if (slowpath(!newisa.nonpointer)) {
                    ClearExclusive(&isa.bits);
                    if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
                    else return sidetable_retain(sideTableLocked);
                }
                // don't check newisa.fast_rr; we already called any RR overrides
                if (slowpath(newisa.isDeallocating())) {
                    ClearExclusive(&isa.bits);
                    if (sideTableLocked) {
                        ASSERT(variant == RRVariant::Full);
                        sidetable_unlock();
                    }
                    if (slowpath(tryRetain)) {
                        return nil;
                    } else {
                        return (id)this;
                    }
                }
                uintptr_t carry;
                newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++
        
                if (slowpath(carry)) {
                    // newisa.extra_rc++ overflowed
                    if (variant != RRVariant::Full) {
                        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)));
        
            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;
        }

代码调用过程与 release 很相似, 之前是减操作,这里是加操作

  1. 在优化 isa 以前,直接存在 SideTable 散列表中

  2. 在优化 isa 以后,肯定优先存在 extra_rc 里 2.1 如果这里存满了,那么会取出一半存放到 SideTable 中去,并将 has_sidetable_rc 标记为 1 (slowpath(carry)) 条件成立时,就是 extra_rc 满了

    • newisa.extra_rc = RC_HALF;sidetable_addExtraRC_nolock(RC_HALF); 分别是半劈存储 😁

    这么操作的目的在于提高性能,因为如果都存在散列表中,当需要release-1时,需要去访问散列表,每次都需要开解锁,比较消耗性能。extra_rc 存储一半的话,可以直接操作 extra_rc 即可,不需要操作散列表。性能会提高很多

    • isabits 为 8字节 = 64 bit,根据结构体位域内存分布,nonpointer 是最低位,bits中的 1ULL<<45(arm64)后就是 extra_rc最低位,通过 addc 函数执行加法运算 (对比 release 通过 subc 实现 -1 运算),也就是要在 extra_rc 的存储空间最低位上去 +1

    • __arm64__

          #   define ISA_MASK        0x0000000ffffffff8ULL
          #   define ISA_MAGIC_MASK  0x000003f000000001ULL
          #   define ISA_MAGIC_VALUE 0x000001a000000001ULL
          #   define ISA_BITFIELD                                                      \
              uintptr_t nonpointer        : 1;                                       \
              uintptr_t has_assoc         : 1;                                       \
              uintptr_t has_cxx_dtor      : 1;                                       \
              uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
              uintptr_t magic             : 6;                                       \
              uintptr_t weakly_referenced : 1;                                       \
              uintptr_t deallocating      : 1;                                       \
              uintptr_t has_sidetable_rc  : 1;                                       \
              uintptr_t extra_rc          : 19
          #   define RC_ONE   (1ULL<<45)
          #   define RC_HALF  (1ULL<<18)

Swift

在了解 Swift 引用计数前,应该先了解下 Swift 类结构,可以先参考笔记 Swift实例对象内存结构HeapObject

在之前简单探索 Swift 源码得知,非继承自 NSObjectSwift 类结构中专门有个 SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS 8字节来存放引用计数,这可比优化后 isa 指针那 19个二进制位 大得多了

通过 Swift 源码可以大概了解, Swift 有三种引用计数

@_silgen_name("swift_retainCount")
public func _getRetainCount(_ Value: AnyObject) -> UInt
@_silgen_name("swift_unownedRetainCount")
public func _getUnownedRetainCount(_ Value: AnyObject) -> UInt
@_silgen_name("swift_weakRetainCount")
public func _getWeakRetainCount(_ Value: AnyObject) -> UInt

Swift 源码看的还不够深,下次得重新编译一个 Xcode 工程来阅读,目前用 VSCode 看起来很不舒服

TBD.

Getting Started

Social

Clone this wiki locally