記憶體管理-弱引用分析

語言: CN / TW / HK

散列表結構分析

image.png ``` bool objc_object::sidetable_tryRetain() {

if SUPPORT_NONPOINTER_ISA

ASSERT(!isa.nonpointer);

endif

SideTable& table = SideTables()[this];

bool result = true;
auto it = table.refcnts.try_emplace(this, SIDE_TABLE_RC_ONE);
auto &refcnt = it.first->second;
if (it.second) {
    // there was no entry
} else if (refcnt & SIDE_TABLE_DEALLOCATING) {
    result = false;
} else if (! (refcnt & SIDE_TABLE_RC_PINNED)) {
    refcnt += SIDE_TABLE_RC_ONE;
}

return result;

} ```

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

``` class StripedMap {

if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR

enum { StripeCount = 8 };

else

enum { StripeCount = 64 };

endif

`` 在 [記憶體管理-retain&realese](https://juejin.cn/post/7006566638833106951)rootRetain函式中我們介紹了當isa不是nonpointer型別的時候就會直接操作散列表,對引用計數進行增加。我們進到sidetable_tryRetain函式可以看到SideTables,代表有很多張表,SideTables在底層是StripedMap,通過StripedMap的結構可以看到,最大可以開 64 張表,如果是真機環境下最大就是 8 張表。而StripedMap` 是對雜湊表的上層封裝。

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

}; ```

通過檢視 SideTable 的結構可以看到 SideTable 中包含了引用計數表跟弱引用表,而上面講的 SideTables 是多張表的形式就是考慮到效能問題,當所有物件都共用一張表的話因為要考慮到多執行緒的問題,當對引用計數操作的時候就會對錶的加鎖和關鎖,會比較消耗效能,當使用多張表的時候,系統可以根據一定的演算法,對不使用的表進行記憶體回收,而不是持續佔用空間。但是也不能每個物件開一張表,因為開表的記憶體太大了,物件很多的話就會有很多的記憶體開闢與回收,也會很消耗效能。所以表的數量要在一個合理的範圍內。

``` id objc_object::sidetable_retain(bool locked) {

if SUPPORT_NONPOINTER_ISA

ASSERT(!isa.nonpointer);

endif

SideTable& table = SideTables()[this];

if (!locked) table.lock();
size_t& refcntStorage = table.refcnts[this];
if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
    refcntStorage += SIDE_TABLE_RC_ONE;
}
table.unlock();

return (id)this;

} ```

sidetable_retain 函式中,SideTables()[this] 根據 this 獲取到當前物件所在的表,size_t& refcntStorage = table.refcnts[this] 根據 this 找到物件在 table 中的儲存空間,然後對 refcntStorage 進行加操作。

弱引用表分析

image.png image.png

如圖我們通過 __weakweakObjc進行修飾,我們在這裡進行斷點,通過彙編除錯可以看到來到了 objc_initWeak 函式,然後我們通過原始碼來看一下 objc_initWeak 函式做了哪些操作。

``` id objc_initWeak(id location, id newObj) { if (!newObj) { location = nil; return nil; }

return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
    (location, (objc_object*)newObj);

} ```

``` // 這裡是 c++ 的模板函式 template 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;

retry: // 這裡判斷物件是否存在舊的引用,如果第一次來到這裡說明沒加入到過弱引用表,就會走到 else 裡面 if (haveOld) { oldObj = *location; oldTable = &SideTables()[oldObj]; } else { oldTable = nil; }

// 這裡判斷是否存在新的引用,如果成立,就根據 newObjc 到 SideTables 中找到 newTable
if (haveNew) {
    newTable = &SideTables()[newObj];
} else {
    // 如果沒有有新的弱引用,newTable 為 nil
    newTable = nil;
}

// 這裡判斷物件是否存在舊的引用,如果存在就進行相關的移除.
if (haveOld) {
    weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}

// 這裡判斷是新的引用
if (haveNew) {
    // 這裡判斷物件是否是 objc_object 型別,是的話就呼叫 weak_register_no_lock 函式
    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;

} ```

``` 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;

if (referent->isTaggedPointerOrNil()) return referent_id;

weak_entry_t *entry;
// 這裡根據物件(referent)判斷 weak_table 是否存在 entry,如果存在就進行追加,往 entry 的 referrers 中新增 referent
if ((entry = weak_entry_for_referent(weak_table, referent))) {
    append_referrer(entry, referrer);
} 
else {
    // 如果 weak_table沒有對應的 entry,就通過 referent 跟 referrer 建立 new_entry
    weak_entry_t new_entry(referent, referrer);
    // 這裡進行記憶體的判斷,並根據規則進行擴容
    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;

} ```

總結如下: * 首先根據物件在 SideTables 中查詢對應的 SideTable * 判斷是否存在舊的引用,如果存在就進行相關的移除 * 判斷是新的引用且判斷物件是否是 objc_object 型別,是的話就呼叫 weak_register_no_lock 函式 * weak_register_no_lock 函式中根據物件(referent)判斷 weak_table 是否存在 entry, * 如果存在就進行追加,往 entryreferrers 中新增 referent

  • 不存在的話,就通過 referentreferrer 進行繫結,建立 new_entry,並且進行記憶體的判斷,並根據規則進行擴容,最後把新的 new_entry 存入到 weak_table

image.png

通過上圖可以看到,首先存在一個全域性的 SideTables,然後 SideTables 中會有不同數量的 SideTable,而且 SideTable 中存在引用計數表跟弱引用計數表 weak_tableweak_table 又根據不同的物件對應不同的 weak_entry_t,而 weak_entry_t 中包含 weak_referrer_t *referrersreferrers 儲存的為 objc_object * 型別 DisguisedPtr<objc_object *> weak_referrer_t

關於 weak 引用計數的問題

image.png

如圖當我們列印 weakObjc 的引用計數的時候,發現等於 2,那麼這是什麼原因呢?我們通過斷點除錯來看一下。

image.png

通過彙編除錯我們可以看到列印 weakObjc 的時候呼叫了 objc_loadWeak 函式,下面我們就來追蹤 objc_loadWeak 函式。

id objc_loadWeak(id *location) { if (!*location) return nil; return objc_autorelease(objc_loadWeakRetained(location)); }

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

SideTable *table;

retry: // 這裡 location 就是 weakObjc obj = location; if (obj->isTaggedPointerOrNil()) return obj;

// 根據 obj 獲取 table
table = &SideTables()[obj];

table->lock();
if (*location != obj) {
    table->unlock();
    goto retry;
}

// 這裡把 obj 賦值給臨時變數 result
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());
    // 這裡 rootTryRetain 會呼叫 rootRetain,所以引用計數會被加 1
    if (! obj->rootTryRetain()) {
        result = nil;
    }
}
else {...}

table->unlock();
return result;
// 出了這個作用域空間後 result 會被 release

} ```

ALWAYS_INLINE bool objc_object::rootTryRetain() { return rootRetain(true, RRVariant::Fast) ? true : false; }

objc_loadWeak 函式中我們可以看到,在這裡會呼叫 rootTryRetain 函式,然後 rootTryRetain 函式呼叫了 rootRetain 函式,然後就會走 rootRetain 流程,所以引用計數會被加 1,但是出了 objc_loadWeak 函式的時候,result 會被 release,所以引用計數又會被減 1。