内存管理-弱引用分析

691 阅读5分钟

散列表结构分析

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