内存管理-弱引用分析

作者: 晨曦的简书 | 来源:发表于2021-09-21 01:26 被阅读0次

    散列表结构分析

    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 进行加操作。

    弱引用表分析


    如图我们通过 __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

    通过上图可以看到,首先存在一个全局的 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 引用计数的问题

    如图当我们打印 weakObjc 的引用计数的时候,发现等于 2,那么这是什么原因呢?我们通过断点调试来看一下。

    通过汇编调试我们可以看到打印 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。

    相关文章

      网友评论

        本文标题:内存管理-弱引用分析

        本文链接:https://www.haomeiwen.com/subject/fceawltx.html