OC內(nèi)存管理-ARC&MRC弦讽、散列表

  • ARCLLVMRuntime配合的結(jié)果。
  • ARC中禁止手動調(diào)用retain/release/retainCount/dealloc
  • ARC新加了weak凰棉、strong屬性關(guān)鍵字

一损拢、 retain 源碼解析

1.1 rootRetain 核心源碼

ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
{
    //TaggedPointer 直接返回
    if (slowpath(isTaggedPointer())) return (id)this;

    bool sideTableLocked = false;
    bool transcribeToSideTable = false;

    isa_t oldisa;
    isa_t newisa;

    oldisa = LoadExclusive(&isa.bits);
......
    do {
        transcribeToSideTable = false;
        newisa = oldisa;
        //純指針
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
            //散列表引用計數(shù) + 1
            else return sidetable_retain(sideTableLocked);
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        //非純指針 nonpointer
        //正在釋放(為了多線程)不做處理。
        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 + 1, RC_ONE 從 extra_rc 的最低位開始+1撒犀。相當(dāng)于extra_rc + 1福压。加滿了標(biāo)記 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;
            //標(biāo)記散列表存儲
            transcribeToSideTable = true;
            //extra_rc 減半
            newisa.extra_rc = RC_HALF;
            //標(biāo)記有散列表
            newisa.has_sidetable_rc = true;
        }
    } while (slowpath(!StoreExclusive(&isa.bits, &oldisa.bits, newisa.bits)));
    //isa extre_rc滿了
    if (variant == RRVariant::Full) {
        if (slowpath(transcribeToSideTable)) {
            // Copy the other half of the retain counts to the side table.
            //散列表引用計數(shù) + 一半
            sidetable_addExtraRC_nolock(RC_HALF);
        }

        if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
    } else {
......
    }

    return (id)this;
}
  • 判斷是否TaggedPointer掏秩,TaggedPointer直接返回。
  • nonpointer散列表引用計數(shù)+1荆姆。
  • 對象正在釋放不進(jìn)行操作蒙幻。
  • nonpointerextra_rc + 1
    • extra_rc超載的情況下has_sidetable_rc設(shè)置為true胆筒。
    • extra_rc減半邮破。
    • 散列表加一半extra_rc

1.2 sidetable_retain

id
objc_object::sidetable_retain(bool locked)
{
#if SUPPORT_NONPOINTER_ISA
    ASSERT(!isa.nonpointer);
#endif
    //獲取對象對應(yīng)的散列表 SideTable
    SideTable& table = SideTables()[this];
    
    if (!locked) table.lock();
    size_t& refcntStorage = table.refcnts[this];
    //引用計數(shù)+1
    if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
        //1 << 2, +2  是在 refcntStorage 上 +2腐泻,因為引用計數(shù)存儲在 SideTable 的 refcntStorage 位置從1開始决乎,不是從0開始。
        //這里+2 相當(dāng)于+ 0b010派桩,只對1號位置加
        refcntStorage += SIDE_TABLE_RC_ONE;
    }
    table.unlock();

    return (id)this;
}
  • 獲取對象對應(yīng)的SideTable
  • 獲取SideTable中的引用計數(shù)表refcnts蚌斩。
  • 從引用計數(shù)表中找到refcntStorage铆惑。
  • refcntStorage +2引用計數(shù)+1,這里+2是因為加到對應(yīng)的位上送膳,從1開始员魏。

1.3 sidetable_addExtraRC_nolock

bool 
objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
{
    ASSERT(isa.nonpointer);
    SideTable& table = SideTables()[this];
    //從SideTable對應(yīng)的引用計數(shù)表中取出對象的引用計數(shù)refcntStorage
    size_t& refcntStorage = table.refcnts[this];
    size_t oldRefcnt = refcntStorage;
    // isa-side bits should not be set here
    ASSERT((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
    ASSERT((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);

    if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true;

    uintptr_t carry;
    //散列表引用計數(shù) + extra_rc的一半。從1號位置開始存叠聋,所以需要 delta_rc << 2
    size_t newRefcnt = 
        addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);
    if (carry) {
        refcntStorage =
            SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);
        return true;
    }
    else {
        refcntStorage = newRefcnt;
        return false;
    }
}
  • 獲取對象對應(yīng)的SideTable撕阎。
  • 獲取SideTable中的引用計數(shù)表refcnts
  • 從引用計數(shù)表中找到refcntStorage碌补。
  • refcntStorage + delta_rc << SIDE_TABLE_RC_SHIF引用計數(shù)+ extra_rc最大值的一半虏束。

散列表和extra_rc各存儲一半是因為extra_rc可以通過isa直接拿到而散列表需要去查找表然后找到對象的引用計數(shù)區(qū)域。散列表還有加解鎖厦章。在extra_rc中操作方便快速镇匀。extra_rc每次存儲挪一半是為了避免在retainrelease頻繁操作的時候而導(dǎo)致散列表頻繁操作。

1.4 retain 流程

retain流程
  • TaggedpPointer直接返回袜啃。
  • nonpointer isa引用計數(shù)表引用計數(shù)+1
  • nonpointer isa extra_rc +1群发。
    • 如果extra_rc上溢出(iOS真機255)晰韵,extra_rc值減半(128)。
    • extra_rc減少一半的值存入引用計數(shù)表熟妓。

二雪猪、 release 源碼解析

2.1 release 核心源碼

ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, objc_object::RRVariant variant)
{
    //判斷是否TaggedPointer,TaggedPointer直接返回
    if (slowpath(isTaggedPointer())) return false;

    bool sideTableLocked = false;

    isa_t newisa, oldisa;
......
retry:
    do {
        newisa = oldisa;
        //非 nonpointer
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            //散列表 引用計數(shù)-1
            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;
        //extra_rc - 1滑蚯。減一后如果extra_rc=0了浪蹂,則標(biāo)記carry抵栈。
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc--
        if (slowpath(carry)) {//跳轉(zhuǎn)underflow
            // don't ClearExclusive()
//            printf("newisa.extra_rc: %d\n",newisa.extra_rc);
            //存儲滿的情況下
            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) {
            //清空extra_rc
            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.
        //將sidetable中取一半(extra_rc 最大值的一半)存到 extra_rc 中。borrow為借過來的值坤次。
        auto borrow = sidetable_subExtraRC_nolock(RC_HALF);
        //散列表中沒有值則標(biāo)記清空散列表
        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 值為 borrowed - 1古劲。extra_rc 發(fā)生溢出了所以-1存儲
            newisa.extra_rc = borrow.borrowed - 1;  // redo the original decrement too
//            printf("newisa.extra_rc111 : %d\n",newisa.extra_rc);
            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;
                //借過來的存入到bits。
                newisa.bits =
                    addc(oldisa.bits, RC_ONE * (borrow.borrowed-1), 0, &overflow);
//                printf("newisa.extra_rc222 : %d\n",newisa.extra_rc);
                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 加回去
                sidetable_addExtraRC_nolock(borrow.borrowed);
                oldisa = LoadExclusive(&isa.bits);
                goto retry;
            }

            // Decrement successful after borrowing from side table.
            //清空sidetable
            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);

    //調(diào)用dealloc
    if (performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
    }
    return true;
}
  • 判斷是否TaggedPointer缰猴,TaggedPointer直接返回产艾。
  • nonpointer isa則直接散列表引用計數(shù)-1
  • 如果對象在釋放直接返回false滑绒。
  • nonpointer isaextra_rc - 1闷堡。
  • extra_rc溢出的情況判斷has_sidetable_rc
  • has_sidetable_rctrue的情況則sidetable減去extra_rc最大值的一半疑故,值存儲到borrow杠览。
  • extra_rc設(shè)置為borrow.borrowed - 1(溢出了要減去1再存儲,相當(dāng)于這次的release)纵势。
  • borrow.remaining == 0的情況則設(shè)置emptySideTable清空對象對應(yīng)的SideTable踱阿。
  • extra_rc0的情況則調(diào)用發(fā)送dealloc消息。

2.2 sidetable_release

uintptr_t
objc_object::sidetable_release(bool locked, bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
    ASSERT(!isa.nonpointer);
#endif
    //獲取對象的 SideTable
    SideTable& table = SideTables()[this];

    bool do_dealloc = false;

    if (!locked) table.lock();
    //獲取table中對象對象的引用計數(shù) refcnts
    auto it = table.refcnts.try_emplace(this, SIDE_TABLE_DEALLOCATING);
    auto &refcnt = it.first->second;
    if (it.second) {
        do_dealloc = true;
    } else if (refcnt < SIDE_TABLE_DEALLOCATING) {//在釋放
        // SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
        do_dealloc = true;
        refcnt |= SIDE_TABLE_DEALLOCATING;
    } else if (! (refcnt & SIDE_TABLE_RC_PINNED)) {
        //引用計數(shù)-1
        refcnt -= SIDE_TABLE_RC_ONE;
    }
    table.unlock();
    if (do_dealloc  &&  performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
    }
    return do_dealloc;
}
  • 通過對象獲取SideTablerefcnts钦铁。
  • refcnts - 2相當(dāng)于引用計數(shù)-1软舌。
  • 如果引用計數(shù)為0則發(fā)送dealloc消息。

2.3 sidetable_subExtraRC_nolock

objc_object::SidetableBorrow
objc_object::sidetable_subExtraRC_nolock(size_t delta_rc)
{
    ASSERT(isa.nonpointer);
    SideTable& table = SideTables()[this];

    RefcountMap::iterator it = table.refcnts.find(this);
    if (it == table.refcnts.end()  ||  it->second == 0) {
        // Side table retain count is zero. Can't borrow.
        return { 0, 0 };
    }
    size_t oldRefcnt = it->second;
    // isa-side bits should not be set here
    ASSERT((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
    ASSERT((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);

    //sidetable 減少 extra_rc 的最大值的一半(這里有位運算平移)
    size_t newRefcnt = oldRefcnt - (delta_rc << SIDE_TABLE_RC_SHIFT);
    ASSERT(oldRefcnt > newRefcnt);  // shouldn't underflow
    it->second = newRefcnt;
    return { delta_rc, newRefcnt >> SIDE_TABLE_RC_SHIFT };
}
  • 通過對象獲取SideTablerefcnts牛曹。
  • newRefcntoldRefcnt減去extra_rc一半佛点。
  • 返回delta_rc以及剩余的newRefcnt

2.4 release流程

release流程
  • TaggedpPointer直接返回false黎比。
  • nonpointer isa引用計數(shù)表引用計數(shù)-1超营。如果引用計數(shù)為0則調(diào)用dealloc返回true,否則返回false焰手。
  • nonpointer isa extra_rc -1糟描。
    • 如果extra_rc下溢出 ,判斷has_sidetable_rc书妻。
      • 沒有引用計數(shù)表則調(diào)用dealloc船响,返回true
      • 有引用計數(shù)表則減去extra_rc最大值的一半(128)存入extra_rc散列表中如果沒有值了則清空散列表躲履,返回false见间。
    • 如果extra_rc != 0 ,返回false工猜。

總結(jié):
retain 針對相應(yīng)引用計數(shù)位+1米诉,開啟nonpointer的情況下,如果引用計數(shù)出現(xiàn)上溢出篷帅,那么開始分開存儲史侣,一半存到散列表拴泌。
release 針對相應(yīng)引用計數(shù)位-1,開始nonpointer的情況下惊橱,如果引用計數(shù)出現(xiàn)下溢出蚪腐,去散列表借來的引用計數(shù) -1 存到extra_rc,依然下溢出則調(diào)用dealloc税朴。

三回季、散列表(SideTable)

retainrelease的源碼中SideTable是通過SideTables獲取的:

SideTable& table = SideTables()[this];

那么證明SideTable是有多張的,SideTables的定義如下:

static StripedMap<SideTable>& SideTables() {
    return SideTablesMap.get();
}

class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
    enum { StripeCount = 8 };
#else
    enum { StripeCount = 64 };
#endif
......
}

iPhone真機下SideTables中有8SideTable正林,其它則為64張泡一。
SideTable對應(yīng)的結(jié)構(gòu):

struct SideTable {
    spinlock_t slock;//os_unfair_lock
    RefcountMap refcnts;//引用計數(shù)表
    weak_table_t weak_table;//弱引用表
......
}

散列表中存儲了鎖、引用計數(shù)表觅廓、弱引用表鼻忠。

那么為什么使用多張表呢?
由于SideTable有加鎖和解鎖杈绸,如果在整個系統(tǒng)中如果共用一張表那么就會有性能消耗(互斥)粥烁。多張表內(nèi)存可以置空回收。不是每個對象開辟一張表為了效率和性能蝇棉。

SideTables結(jié)構(gòu)圖下:

image.png

3.1 引用計數(shù)(retainCount)

inline uintptr_t 
objc_object::rootRetainCount()
{
    //TaggedPointer 對象指針強轉(zhuǎn)返回。
    if (isTaggedPointer()) return (uintptr_t)this;

    sidetable_lock();
    isa_t bits = __c11_atomic_load((_Atomic uintptr_t *)&isa.bits, __ATOMIC_RELAXED);
    //nonpointer
    if (bits.nonpointer) {
        //extra_rc芥永,之前的版本為 extra_rc + 1篡殷。由于之前的版本 alloc 的時候 extra_rc 不進(jìn)行 +1。目前版本 alloc 的時候進(jìn)行了賦值 1埋涧。
        uintptr_t rc = bits.extra_rc;
        if (bits.has_sidetable_rc) {
            // extra_rc + 散列表引用計數(shù)
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }
    
    sidetable_unlock();
    //非 nonpointer 獲取散列表中引用計數(shù)板辽。
    return sidetable_retainCount();
}
  • TaggedPointer直接返回對象的地址強轉(zhuǎn)為unsigned long
  • nonpointer返回extra_rc + 引用計數(shù)表中引用計數(shù)棘催。extra_rc這里直接返回沒有+1劲弦,之前的版本會有+1操作。alloc之前不會對extra_rc賦值為1醇坝,現(xiàn)在版本會賦值為1邑跪。
  • nonpointer直接返回引用計數(shù)表中引用計數(shù)。

alloc的過程中在進(jìn)行initIsa的時候?qū)?code>extra_rc進(jìn)行了賦值:

image.png

SideTable數(shù)據(jù)內(nèi)容如下:

(lldb) p table
(SideTable) $5 = {
  slock = {
    mLock = (_os_unfair_lock_opaque = 775)
  }
  refcnts = {
    Buckets = 0x0000000102b04080
    NumEntries = 1
    NumTombstones = 0
    NumBuckets = 4
  }
  weak_table = {
    weak_entries = 0x0000000000000000
    num_entries = 0
    mask = 0
    max_hash_displacement = 0
  }
}

refcnts中存儲了引用計數(shù)的Buckets呼猪,其中存儲了DisguisedPtr<objc_object>(包裝了引用計數(shù))画畅,與關(guān)聯(lián)對象的存儲有些類似。

3.2 弱引用

有如下代碼:

NSObject *obj = [NSObject alloc];
NSLog(@"%zd - %@",(long)CFGetRetainCount((__bridge CFTypeRef)(obj)),obj);//1
__weak typeof(id) weakObjc = obj;
NSLog(@"%zd - %@",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc)),weakObjc);//2
NSLog(@"%zd - %@",(long)CFGetRetainCount((__bridge CFTypeRef)(obj)),obj);//1

運行輸出:

1 - <NSObject: 0x101622330>
2 - <NSObject: 0x101622330>
1 - <NSObject: 0x101622330>

按照正常理解weak不增加引用計數(shù)宋距,obj輸出1沒問題轴踱。weakObjc的引用計數(shù)為什么輸出2

3.2.1 弱引用表

要了解weak的引用計數(shù)首先要清楚weak表的存儲邏輯谚赎。通過匯編跟蹤發(fā)現(xiàn)__weak修飾的變量會進(jìn)入objc_initWeak

image.png

那么__weak是怎么與objc_initWeak關(guān)聯(lián)起來的呢淫僻?
llvm中有相關(guān)的映射诱篷,weak__weak最終映射到了objc_initWeak

image.png

弱引用的存儲與釋放:

id
objc_initWeak(id *location, id newObj)
{
    //對象不存在直接返回。
    if (!newObj) {
        //weak 指針置為 nil
        *location = nil;
        return nil;
    }
    //執(zhí)行存儲操作
    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}

void
objc_destroyWeak(id *location)
{
    //與init同一個函數(shù)雳灵。傳遞 newObj 為 nil
    (void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
        (location, nil);
}

最終都會調(diào)用同一個函數(shù)storeWeak棕所。

3.2.1.1 storeWeak

//c++ 模板參數(shù)
template <HaveOld haveOld, HaveNew haveNew,
          enum CrashIfDeallocating crashIfDeallocating>
static id 
storeWeak(id *location, objc_object *newObj)
{
    ASSERT(haveOld  ||  haveNew);
    if (!haveNew) ASSERT(newObj == nil);

    Class previouslyInitializedClass = nil;
    id oldObj;
    //舊表
    SideTable *oldTable;
    //新表
    SideTable *newTable;

    // Acquire locks for old and new values.
    // Order by lock address to prevent lock ordering problems. 
    // Retry if the old value changes underneath us.
 retry:
    //整體是SideTables
    if (haveOld) {//是否有舊值,第一次進(jìn)來沒有细办。
        oldObj = *location;
        //取舊表
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (haveNew) {
        //取weak指針對應(yīng)的新表地址
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }
    //鎖定兩張表
    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
......

    // Clean up old value, if any.
    if (haveOld) {//釋放
        //清空舊值
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    // Assign new value, if any.
    if (haveNew) {
        newObj = (objc_object *)
            //存儲新值
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
        // weak_register_no_lock returns nil if weak store should be rejected

        // Set is-weakly-referenced bit in refcount table.
        if (!newObj->isTaggedPointerOrNil()) {
            newObj->setWeaklyReferenced_nolock();
        }

        // Do not set *location anywhere else. That would introduce a race.
        *location = (id)newObj;
    }
    else {
        // No new value. The storage is not changed.
    }
    
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

    callSetWeaklyReferenced((id)newObj);

    return (id)newObj;
}
  • 獲取oldTablenewTable橙凳,newTable用來存儲,oldTable用來釋放笑撞。
  • 通過SideTables獲取obj對應(yīng)的SideTable岛啸。
  • 如果是釋放調(diào)用weak_unregister_no_lock釋放弱引用指針。參數(shù)傳遞SideTableweak_table以及obj和弱引用指針茴肥。
  • 如果是存儲調(diào)用weak_register_no_lock存儲弱引用指針坚踩。參數(shù)傳遞SideTableweak_table以及obj和弱引用指針。

3.2.1.2 weak_unregister_no_lock

void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, 
                        id *referrer_id)
{
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;

    weak_entry_t *entry;

    if (!referent) return;
    //找到對象的 weak_entry_t
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        //將弱引用指針從weak_entry_t中移除瓤狐。
        remove_referrer(entry, referrer);
        bool empty = true;
        if (entry->out_of_line()  &&  entry->num_refs != 0) {
            empty = false;
        }
        else {
            for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
                if (entry->inline_referrers[i]) {
                    empty = false; 
                    break;
                }
            }
        }

        if (empty) {
            //如果entry為空了瞬铸,則將entry從整個weak_table中移除
            weak_entry_remove(weak_table, entry);
        }
    }
}
  • weak_table中找到對象對應(yīng)的weak_entry_t
  • 調(diào)用remove_referrer遍歷weak_entry_tinline_referrers或者referrers將對應(yīng)index位置的值置為nil础锐,并且num_refs - 1嗓节。
  • 如果weak_entry_t中已經(jīng)沒有值了,則調(diào)用weak_entry_removeentryweak_table中清除并且釋放空間皆警。

3.2.1.3 weak_register_no_lock

//對象對應(yīng)的全局弱引用表拦宣,對象指針,弱引用指針
id 
weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions)
{
    //對象
    objc_object *referent = (objc_object *)referent_id;
    //弱引用指針
    objc_object **referrer = (objc_object **)referrer_id;

  ......

    // now remember it and where it is being stored
    weak_entry_t *entry;
    //根據(jù)弱引用對象從weak_table中找出weak_entry_t
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        //將弱引用指針加入entry
        append_referrer(entry, referrer);
    } 
    else {
        //通過弱引用指針與對象創(chuàng)建new_entry
        weak_entry_t new_entry(referent, referrer);
        //weak_table擴容
        weak_grow_maybe(weak_table);
        //將new_entry插入weak_table
        weak_entry_insert(weak_table, &new_entry);
    }

    // Do not set *referrer. objc_storeWeak() requires that the 
    // value not change.
    //返回對象
    return referent_id;
}
  • weak_table中找到對象對應(yīng)的weak_entry_t信姓。
  • 找到對應(yīng)的weak_entry_t調(diào)用append_referrer將弱引用指針加入weak_entry_t(因為釋放過程中有置空操作鸵隧,所以找空位nil加入,這個過程中可能會進(jìn)行擴容)意推。
  • 找不到則根據(jù)對象和弱引用指針創(chuàng)建weak_entry_t豆瘫。
    • 調(diào)用weak_grow_maybe嘗試擴容。
    • 調(diào)用weak_entry_insert將創(chuàng)建的weak_entry_t加入weak_table菊值。

散列表完整結(jié)構(gòu):


散列表結(jié)構(gòu)

3.2.2 weak 的引用計數(shù)

到目前為止仍然解釋不了為什么之前的案例弱引用計數(shù)為2外驱,修改代碼如下:

NSObject *obj = [NSObject alloc];
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(obj)),obj,&obj);//1
__weak typeof(id) weakObjc = obj;
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc)),weakObjc,&weakObjc);//2
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(obj)),obj,&obj);//1

NSObject *obj2 = obj;
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(obj)),obj,&obj);//2
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(obj2)),obj2,&obj2);//2
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc)),weakObjc,&weakObjc);//3

__weak typeof(id) weakObjc2 = obj;
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(obj)),obj,&obj);//2
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc)),weakObjc,&weakObjc);//3
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc2)),weakObjc2,&weakObjc2);//3

輸出:

1 - <NSObject: 0x101a1a730> - 0x7ffeefbff508
2 - <NSObject: 0x101a1a730> - 0x7ffeefbff4f8
1 - <NSObject: 0x101a1a730> - 0x7ffeefbff508
2 - <NSObject: 0x101a1a730> - 0x7ffeefbff508
2 - <NSObject: 0x101a1a730> - 0x7ffeefbff4f0
3 - <NSObject: 0x101a1a730> - 0x7ffeefbff4f8
2 - <NSObject: 0x101a1a730> - 0x7ffeefbff508
3 - <NSObject: 0x101a1a730> - 0x7ffeefbff4f8
3 - <NSObject: 0x101a1a730> - 0x7ffeefbff4e8

obj引用計數(shù)為2很好理解,weakObjc多次指向也只增加了一次俊性。weakObjc的引用計數(shù)看著是對象的引用計數(shù)+weak1次略步。

CFGetRetainCount調(diào)用的是retainCount,那么顯然獲取的是obj的引用計數(shù)定页,那么多的1肯定做了額外的處理趟薄。
有如下代碼,對NSLog打斷點:

NSObject *obj = [NSObject alloc];
__weak typeof(id) weakObjc = obj;
NSLog(@"%zd",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc)));

在調(diào)用retainCount之前調(diào)用了objc_loadWeakRetained:

image.png

3.2.2.1 objc_loadWeakRetained

id
objc_loadWeakRetained(id *location)
{
    id obj;
    id result;
    Class cls;

    SideTable *table;
    
 retry:
    // fixme std::atomic this load
    //通過 weak 指針獲取 obj 臨時變量典徊。此時引用計數(shù)仍然不變杭煎。
    obj = *location;
    if (obj->isTaggedPointerOrNil()) return obj;
    
    table = &SideTables()[obj];
    
    table->lock();
    if (*location != obj) {
        table->unlock();
        goto retry;
    }
    //可以嘗試 table->unlock() 然后讀取 _objc_rootRetainCount(obj)
    result = obj;

    cls = obj->ISA();
    if (! cls->hasCustomRR()) {
        // Fast case. We know +initialize is complete because
        // default-RR can never be set before then.
        ASSERT(cls->isInitialized());
        //引用計數(shù) +1恩够,此時 obj 的引用計數(shù)變了。
        if (! obj->rootTryRetain()) {
            result = nil;
        }
    }
    else {
        if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) {
            BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL))
                lookUpImpOrForwardTryCache(obj, @selector(retainWeakReference), cls);
            if ((IMP)tryRetain == _objc_msgForward) {
                result = nil;
            }
            else if (! (*tryRetain)(obj, @selector(retainWeakReference))) {
                result = nil;
            }
        }
        else {
            table->unlock();
            class_initialize(cls, obj);
            goto retry;
        }
    }
    //這個時候返回的`retainCount`就多了1羡铲。
    table->unlock();
    return result;
}
  • 獲取弱引用指針對應(yīng)的obj蜂桶。
  • 調(diào)用rootTryRetainobj引用計數(shù)+1

驗證:


image.png

在調(diào)用了objc_loadWeakRetained后調(diào)用了retainCount獲取obj的引用計數(shù)也切。然后調(diào)用objc_release釋放這次增加的引用計數(shù)扑媚。

weak并不會增加引用計數(shù),CFGetRetainCount如果獲取的是weak指針的引用計數(shù)會先調(diào)用objc_loadWeakRetained對對象的引用計數(shù)+1雷恃,再調(diào)用retainCount獲取引用計數(shù)疆股,然后調(diào)用objc_release對對象的引用計數(shù)-1

那么為什么weak的引用計數(shù)要臨時+1呢倒槐?
為了在CFGetRetainCount的過程中旬痹,weakObjc不被釋放。

__weak typeof(id) weakObjc = nil;
{
    NSObject *obj = [NSObject alloc];
    NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(obj)),obj,&obj);
    weakObjc = obj;
    NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(obj)),obj,&obj);
    NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc)),weakObjc,&weakObjc);
}
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc)),weakObjc,&weakObjc);

上面的代碼雖然會崩潰讨越,仍然可以斷點查看weakObjc信息:

image.png

弱引用指針指向的對象已經(jīng)釋放了两残,弱引用指針還沒有釋放,出了弱引用指針作用域才釋放把跨。這么做的好處是弱引用指針的管理與對象的管理完全分開了人弓。

弱引用表調(diào)用流程:

  • 弱引用指針存儲在全局SideTables中。
  • 通過對象獲取SideTable取到其中的weak_table着逐。
  • 創(chuàng)建weak_entry_t票从,將弱引用指針包裝后加入創(chuàng)建的weak_entry_treferrersinline_referrers)中。
  • 判斷是否需要擴容weak_table滨嘱。
  • 將創(chuàng)建的weak_entry_t加入weak_table中。
弱引用存儲銷毀調(diào)用邏輯

四浸间、strong & unsafe_unretain

NSObject *obj = [NSObject alloc];
NSObject *obj1 = obj;
HPObject *objc = [HPObject alloc];
objc.objc = obj;

strong修飾的屬性或者變量太雨,當(dāng)屬性變量賦值的時候會調(diào)用objc_storeStrong(編譯時期確定):

image.png

image.png

image.png

objc_storeStrong源碼如下:

void
objc_storeStrong(id *location, id obj)
{
    //舊值
    id prev = *location;
    if (obj == prev) {
        return;
    }
    //retain 新值
    objc_retain(obj);
    //賦值新值給指針
    *location = obj;
    //release 舊值
    objc_release(prev);
}

objc_storeStrong過程中會先retain新值然后賦值,最后release舊值魁蒜。
在源碼中會根據(jù)變量的修飾符來確定調(diào)用的方法:

image.png

如下代碼:

@property (nonatomic, strong) NSObject *objc;
@property (nonatomic, weak) NSObject *objc1;
@property (nonatomic, unsafe_unretained) NSObject *objc3;

編譯后對應(yīng)的匯編偽代碼:

-(void)setObjc:(void *)arg2 {
    objc_storeStrong(self + 0x40, arg2);
    return;
}

-(void)setObjc1:(void *)arg2 {
    objc_storeWeak(self + 0x48, arg2);
    return;
}

-(void)setObjc3:(void *)arg2 {
    self->_objc3 = arg2;
    return;
}
  • strong修飾的變量底層會調(diào)用objc_storeStrong先進(jìn)行新值的retain然后賦值囊扳,最后舊值進(jìn)行release
  • weak底層調(diào)用objc_storeWeakweak指針加入弱引用表中兜看。在dealloc的時候會自動將weak指針置為nil锥咸。
  • unsafe_unretained直接用新值賦值指針,在dealloc的時候并不會自動置為nil细移,可能會造成野指針訪問搏予。

總結(jié):

  • retain 針對相應(yīng)引用計數(shù)位+1,開啟nonpointer的情況下弧轧,如果引用計數(shù)出現(xiàn)上溢出雪侥,那么開始分開存儲碗殷,一半存到散列表。
  • release 針對相應(yīng)引用計數(shù)位-1速缨,開啟nonpointer的情況下锌妻,如果引用計數(shù)出現(xiàn)下溢出,去散列表借來的引用計數(shù) -1 存到extra_rc旬牲,依然下溢出則調(diào)用dealloc仿粹。
  • 散列表
    • iPhone真機下SideTables中有8SideTable,其它則為64張原茅。
    • 每張SideTable包含了 引用計數(shù)表 和 弱引用表吭历。
  • 引用計數(shù)表(RefcountMap)的Buckets存儲了對象對應(yīng)的引用計數(shù)的包裝。
  • 弱引用表(weak_table_t)的weak_entries存儲了對象的弱引用指針weak_entry_t员咽,weak_entry_t中存儲了指向的對象和指向該對象的弱引用指針集合referrers毒涧。referrer中存儲了包裝的弱引用指針。
  • 引用計數(shù)
    • TaggedPointer直接返回對象的地址強轉(zhuǎn)為unsigned long贝室。
    • nonpointer返回extra_rc + 引用計數(shù)表中引用計數(shù)契讲。extra_rc這里直接返回沒有+1,之前的版本會有+1操作滑频。alloc之前不會對extra_rc賦值為1捡偏,現(xiàn)在版本會賦值為1
    • nonpointer直接返回引用計數(shù)表中引用計數(shù)峡迷。
    • weak并不會增加引用計數(shù)银伟,CFGetRetainCount會調(diào)用objc_loadWeakRetainedweak指向的對象引用計數(shù)+1,調(diào)用完retainCount后調(diào)用objc_release對引用計數(shù)-1绘搞。
  • 弱引用表與對象是分開管理的彤避,各自在作用域處理自身邏輯。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末夯辖,一起剝皮案震驚了整個濱河市琉预,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蒿褂,老刑警劉巖圆米,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異啄栓,居然都是意外死亡娄帖,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門昙楚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來近速,“玉大人,你說我怎么就攤上這事∈福” “怎么了永淌?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長佩耳。 經(jīng)常有香客問我遂蛀,道長,這世上最難降的妖魔是什么干厚? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任李滴,我火速辦了婚禮,結(jié)果婚禮上蛮瞄,老公的妹妹穿的比我還像新娘所坯。我一直安慰自己,他們只是感情好挂捅,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布芹助。 她就那樣靜靜地躺著,像睡著了一般闲先。 火紅的嫁衣襯著肌膚如雪状土。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天伺糠,我揣著相機與錄音蒙谓,去河邊找鬼。 笑死训桶,一個胖子當(dāng)著我的面吹牛累驮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播舵揭,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼谤专,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了午绳?” 一聲冷哼從身側(cè)響起毒租,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎箱叁,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惕医,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡耕漱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了抬伺。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片螟够。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出妓笙,到底是詐尸還是另有隱情若河,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布寞宫,位于F島的核電站萧福,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏辈赋。R本人自食惡果不足惜鲫忍,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望钥屈。 院中可真熱鬧悟民,春花似錦、人聲如沸篷就。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至锈玉,卻和暖如春船老,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背做鹰。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留鼎姐,地道東北人钾麸。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像炕桨,于是被迫代替她去往敵國和親饭尝。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容