34.iOS底层学习之内存管理散列表SideTable

本章提纲:
1.散列表SideTable
2.弱引用表

上一篇文章最后看到关于Retain的操作里面有关于SideTable相关的内容,这一篇主要来研究一下SideTable。我们接着Retain的源码操作来看。

1.散列表SideTable

  • rootRetain详解
objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
{
    //判断是小对象类型 不进行retain 直接返回
    if (slowpath(isTaggedPointer())) return (id)this;

    //默认不开启散列表
    bool sideTableLocked = false;
    //默认不将引用计数转换到散列表
    bool transcribeToSideTable = false;

    //旧的isa
    isa_t oldisa;
    //新的isa
    isa_t newisa;

    //原子加载isa
    oldisa = LoadExclusive(&isa.bits);

    // 支持SUPPORT_NONPOINTER_ISA的情况下的优化
    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;
        //不支持nonpointer
        if (slowpath(!newisa.nonpointer)) {
            //清除isa的bits
            ClearExclusive(&isa.bits);
            //传进来的tryRetain 为YES 调用sidetable_tryRetain 散列表尝试调用Retain
                                                                    //如果对象正在销毁 返回空
            if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
            else return sidetable_retain(sideTableLocked); //引用计数为+1
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        //isa处于一个正在释放中的状态 那么执行retain都是无效的
        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
            //variant 为Fast或者FastOrMsgSend的状态 去执行rootRetain_overflow
            if (variant != RRVariant::Full) {
                ClearExclusive(&isa.bits);
                //去尝试执行rootRetain_overflow 这个方法实际上调用的是 rootRetain 但是RRVariant 传的又是Full
                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;
            //因为此时已经满了 将extra_rc置为原来的一半,也就是最高位置为0
            //这样下次使用引用计数的时候优先去使用isa中的引用计数标志位来记录 而不是直接使用引用计数表
            //引用计数表要通过索引去查询 比直接处理isa的效率要低
            newisa.extra_rc = RC_HALF;
            //散列表使用引用计数的标志位 为true
            newisa.has_sidetable_rc = true;
        }
    } while (slowpath(!StoreExclusive(&isa.bits, &oldisa.bits, newisa.bits)));

    //把一半的引用计数存到side table 使引用计数的标志位空出来一半 也相当于是一半扩容了其实
    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;
}

加了一部分注释,这个引用计数的操作主要有三个部分:
1、如果是小对象类型isTaggedPointer那么什么都不做,直接返回;
2、非小对象类型,判断isa的状态,如果处于Deallocating那么也啥都不做,做了也没用反正;
3、非小对象类型,并且不处于Deallocating又分为两种情况

  • 3.1 isa表示引用计数的位没有满,直接进行rc+1返回;
  • 3.2 如果引用计数满了,那么要开始使用引用计数表进行协助计数,isa中的引用计数减半RC_HALF,也就是最高位置为0,然后把减半的引用计数存到引用计数表中,has_sidetable_rc置为true就是开启了引用计数表进行计数。

之所以这么做,是为了下一次再去进行引用计数+1的计算,优先去在isa进行处理,有一半的引用计数被存到了SideTable中,那么被减半的isa中存的引用计数还可以再存一半才满,以此循环,查表的效率要比直接使用isa要低,所以优先去操作isa中的计数。

  • sidetable_tryRetain
bool
objc_object::sidetable_tryRetain()
{
#if SUPPORT_NONPOINTER_ISA
    ASSERT(!isa.nonpointer);
#endif
    //获取相应的table
    SideTable& table = SideTables()[this];

    // NO SPINLOCK HERE
    // _objc_rootTryRetain() is called exclusively by _objc_loadWeak(), 
    // which already acquired the lock on our behalf.

    // fixme can't do this efficiently with os_lock_handoff_s
    // if (table.slock == 0) {
    //     _objc_fatal("Do not call -_tryRetain.");
    // }

    bool result = true;
    //try_emplace 如果该对象在buckets中不存在会插入一个新值,如果存在会返回这个值
    //try_emplace 有两个返回值 第一个返回值是遍历器,第二个返回值代表是否存在
    auto it = table.refcnts.try_emplace(this, SIDE_TABLE_RC_ONE);
    auto &refcnt = it.first->second;
    if (it.second) {
        // there was no entry //没有相关的entry 是第一次插入到buckets中
    } else if (refcnt & SIDE_TABLE_DEALLOCATING) {
        //当前对象正在销毁中
        result = false;
    } else if (! (refcnt & SIDE_TABLE_RC_PINNED)) {
        //没有溢出 并且引用计数+1
        refcnt += SIDE_TABLE_RC_ONE;
    }
    
    return result;
}

这部分去查引用计数表,如果对象已经存在并且没有引用计数没有达到最大值,进行引用计数+1,如果不存在那么为这个对象创建一个引用计数表,插入到相关的map中。

  • sidetable_retain
id
objc_object::sidetable_retain(bool locked)
{
#if SUPPORT_NONPOINTER_ISA
    ASSERT(!isa.nonpointer);
#endif
    //拿到对应的SideTable
    SideTable& table = SideTables()[this];
    
    if (!locked) table.lock();
    
    //获取RefcountMap 的大小
    size_t& refcntStorage = table.refcnts[this];
    //RefcountMap没有满 那么进行 引用计数位+1的操作
    if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
        refcntStorage += SIDE_TABLE_RC_ONE;
    }
    table.unlock();
    return (id)this;
}

RefcountMap 的大小没有满,进行+1操作。

  • rootRetain_overflow
objc_object::rootRetain_overflow(bool tryRetain)
{
    return rootRetain(tryRetain, RRVariant::Full);
}

实际上调用的还是rootRetain

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

这个结构中包含一个spinlock_t互斥锁;
一个RefcountMap的引用计数表;
还一个weak_table_t弱引用表;

通过上面👆🏻的代码,来看下SideTable的读取,是通过SideTables()[this]方法进行获取的,把对象的指针传进去,拿到对应的SideTable,而SideTables我们进一步查看:

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

ExplicitInit的作用是生成一个模板类型Type的实例。而StripedMap<SideTable>& SideTables()实际上返回了全局静态StripedMap<SideTable>。简单看下StripedMap的结构:

template<typename T>
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
    enum { StripeCount = 8 };
#else
    enum { StripeCount = 64 };
#endif

    struct PaddedT {
        T value alignas(CacheLineSize);
    };

    PaddedT array[StripeCount];

    //哈希计算取出相应的value值
    static unsigned int indexForPointer(const void *p) {
        uintptr_t addr = reinterpret_cast<uintptr_t>(p);
        return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
    }

 public:
    //运算符`[]`重载,所以SideTable[this] = SideTable[indexForPointer(this)].value
    T& operator[] (const void *p) { 
        return array[indexForPointer(p)].value; 
    }
    const T& operator[] (const void *p) const { 
        return const_cast<StripedMap<T>>(this)[p]; 
    }
  //此处省略若干行
}

这个StripedMap在真机下StripeCount是8,模拟器下StripeCount是64。
方法indexForPointer通过对象的地址,来确定是在array中的第几个元素,进而拿到哈希值。
而关键的地方在于,这个类对[]运算符进行了重载,前面取出对应的SideTable用的方法是SideTables()[this],它的翻译过来的实际操作是:SideTables().array[indexForPointer(this)].value,就是先通过indexForPointer计算出位置,在通过array[index]取出值,就是对应的SideTable

2.弱引用表

上面的SideTable结构中包括weak_table_t,它的结构如下:

/**
 * The global weak references table. Stores object ids as keys,
 * and weak_entry_t structs as their values.
 */
struct weak_table_t {
    weak_entry_t *weak_entries;
    size_t    num_entries;
    uintptr_t mask;
    uintptr_t max_hash_displacement;
};

这是全局的弱引用表,对象的id作为key,weak_entry_t是对应的值。

  • weak_entries哈希数组,用来存储弱引用对象的相关信息weak_entry_t
  • num_entries数组个数
  • mask哈希数组长度,会参与哈希计算。
  • max_hash_displacement 最大哈希偏移值。

weak_entry_t结构

struct weak_entry_t {
    DisguisedPtr<objc_object> referent;
    union {
        struct {
            weak_referrer_t *referrers;
            uintptr_t        out_of_line_ness : 2;
            uintptr_t        num_refs : PTR_MINUS_2;
            uintptr_t        mask;
            uintptr_t        max_hash_displacement;
        };
        struct {
            // out_of_line_ness field is low bits of inline_referrers[1]
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };
  //省略部分代码
};
  • referent被弱引用的对象
  • union联合体中,两个结构体共用一块内存,动态数组和非动态数组只用一种。

通过上面的结构可以了解到这个弱引用表是在SideTables->SideTable->weak_table_t最终找到被弱引用者的引用weak_entry_t这样的层级结构。

举个例子:
比如,b,c,d分别弱引用了a,那么通过一系列的查找,a的弱引用表里会存了b,c,d这三个对象的地址,b,c,d这三个对象的地址对应的就是weak_entry_t这样一个层级。

所以通过以上的分析,我们也能够明白,为什么弱引用对象不会增加对象的引用计数,因为从本质上这就是两个表,当进行弱引用时,不会去操作对象的引用计数表,而是操作对象的弱引用表。

附一张结构图:


13591646280567_.pic_hd.jpg

下面接着来看弱引用相关的源码,看看具体的对弱引用对象的操作,以及弱引用表中的对象是如何置为nil的。

2.1弱引用相关源码

前面看相关SideTable的内容都是在文件objc-weak.h中,我们搜索下这个文件,可以发现有相关的.m,于是可以在.m中可以看到一些操作的具体实现。我们先来看看weak是如何插入到weak表中的。

  • weak_entry_insert
/** 
 * Add new_entry to the object's table of weak references.
 * Does not check whether the referent is already in the table.
 * 添加新的entry到对象的weak引用表里
 * 不会检查这个引用是不是已经在这个表里
 */
static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)
{
    //拿到weak_table中的所有weak_entries
    weak_entry_t *weak_entries = weak_table->weak_entries;
    ASSERT(weak_entries != nil);
    //获取new_entry的index
    size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask);
    size_t index = begin;
    
    //偏移量为0
    size_t hash_displacement = 0;
    //不为空 说明该index对应的位置已经有弱引用对象
    while (weak_entries[index].referent != nil) {
        //往下挪一个index的位置
        index = (index+1) & weak_table->mask;
        //index又找回到了起始位置 说明找了一圈都没找到 调用bad_weak_table 是一个异常
        if (index == begin) bad_weak_table(weak_entries);
        //偏移量++
        hash_displacement++;
    }

    //找到了没存entry为空的位置 在相应的index写入新的new_entry的地址
    weak_entries[index] = *new_entry;
    //num_entries数增加1
    weak_table->num_entries++;

    //如果最大的偏移值比原来的大 那么更新最大偏移值到weak_table中
    if (hash_displacement > weak_table->max_hash_displacement) {
        weak_table->max_hash_displacement = hash_displacement;
    }
}

插入新的entry的流程
1.获取到对象的弱引用表;

2.通过new_entry进行哈希计算,拿到第一次应该插入到weak表中的初始位置,用临时变量begin记录下,并且初始化index。

3.开始进行遍历,如果在weak_entries该index位置中的内容不为空,说明哈希冲突了,把index进行后移,直到找到第一个为空的位置,跳出循环。

  • 那么在循环中,如果index转了一圈又回到了begin初始位置,说明没找到空位置可以插入,此时会调用bad_weak_table进行报错。
  • 在循环中index每后移一次,哈希冲突都会进行+1的操作记录一次,也就是最大偏移量会增加一次。

4.找到了为空的位置,那么把新值new_entry的指针插入到这个位置中,更新num_entries,进行比较是否需要更新最大偏移量的值。

  • storeWeak
//存储weak
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:
    //有旧值 取出旧值给oldTable 不存在oldTable置为nil
    if (haveOld) {
        //根据传入的地址获取到旧值
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    
    //有新值 取出新值给newTable 没新值newTable置为nil
    if (haveNew) {
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }

    //加锁
    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);

    //前边刚赋值过旧值 它的location应该和oldObj的一致,如果不一致说明别的线程修改过 那么重新赋值oldObj 重新记录
    if (haveOld  &&  *location != oldObj) {
        //解锁
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
        //重新赋值 保持一致
        goto retry;
    }

    // Prevent a deadlock between the weak reference machinery
    // and the +initialize machinery by ensuring that no 
    // weakly-referenced object has an un-+initialized isa.
    //有新值
    if (haveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        //如果isa还没有初始化 那么先初始化isa 然后重试
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            class_initialize(cls, (id)newObj);

            // If this class is finished with +initialize then we're good.
            // If this class is still running +initialize on this thread 
            // (i.e. +initialize called storeWeak on an instance of itself)
            // then we may proceed but it will appear initializing and 
            // not yet initialized to the check above.
            // Instead set previouslyInitializedClass to recognize it on retry.
            
            // 如果该类还没有初始化完成,例如在 +initialize 中调用了 storeWeak 方法,
            // 也就是会进入这里面,进而设置  previouslyInitializedClass  以在重试时识别它
            // 这里记录一下previouslyInitializedClass, 防止if分支再次进入 造成死循环
            previouslyInitializedClass = cls;

            goto retry;
        }
    }

    // Clean up old value, if any. 清除旧值 如果该weak指针 弱引用过别的对象 要从旧对象的weak表里清除该引用
    if (haveOld) {
        //调用清除方法
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    // Assign new value, if any. 注册新值到表里
    if (haveNew) {
        newObj = (objc_object *)
        // (1) 调用weak_register_no_lock方法,将weak ptr的地址记录到newObj对应的weak_entry_t中
            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.
        // (2) 更新newObj的isa的weakly_referenced bit标志位
        if (!newObj->isTaggedPointerOrNil()) {
            newObj->setWeaklyReferenced_nolock();
        }

        // Do not set *location anywhere else. That would introduce a race.
        // (3)*location 赋值,也就是将weak ptr直接指向了newObj。可以看到,这里并没有将newObj的引用计数+1
        // 将weak ptr指向object
        *location = (id)newObj;
    }
    else {
        // No new value. The storage is not changed.
    }
    
    //解锁
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

    // This must be called without the locks held, as it can invoke
    // arbitrary code. In particular, even if _setWeaklyReferenced
    // is not implemented, resolveInstanceMethod: may be, and may
    // call back into the weak reference machinery.
    callSetWeaklyReferenced((id)newObj);

    return (id)newObj;
}

该方法比较长核心部分有三个:
1、对旧值的处理,如果weak指针引用过旧值,那么要先在旧值的weak表中清除掉这个weak指针的地址。
2、新值的插入。新值的插入在清除完毕旧值的基础上进行该weak指针在新对象的weak表中插入。
3、更新weakly_referenced标志位,更新location到新对象上。

其中包括清除方法weak_unregister_no_lock和注册weak_register_no_lock方法:

  • weak_unregister_no_lock
    加了详细的注释。整个移除的过程很严谨,会去逐一判断,从inline固定的四个引用计数长度判断,再去判断out_line动态数组是不是使用了,再到判断空表是不是需要表移除。


    weak_unregister_no_lock
  • weak_register_no_lock

/** 
 * Registers a new (object, weak pointer) pair. Creates a new weak
 * object entry if it does not exist.
 * 
 * @param weak_table The global weak table.
 * @param referent The object pointed to by the weak reference.
 * @param referrer The weak pointer address.
 */
//注册一个新的objct,weak pointer对,如果objct的entry不存在那么会创建一个新的weak
// weak_table 目标被弱引用对象所存储的表
// referent_id 被所引用的对象
// referrer_id 要被添加的弱引用指针
// crashIfDeallocating 如果对象正在被释放时是否崩溃
//weak_register_no_lock(&newTable->weak_table, (id)newObj, location,crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
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;

    // ensure that the referenced object is viable
    // 确保被引用的对象可用没有在析构,同时应该支持weak引用
    if (deallocatingOptions == ReturnNilIfDeallocating ||
        deallocatingOptions == CrashIfDeallocating) {
        bool deallocating;
        if (!referent->ISA()->hasCustomRR()) {
            deallocating = referent->rootIsDeallocating();
        }
        else {
            // Use lookUpImpOrForward so we can avoid the assert in
            // class_getInstanceMethod, since we intentionally make this
            // callout with the lock held.
            // referent是否支持weak引用
            auto allowsWeakReference = (BOOL(*)(objc_object *, SEL))
            lookUpImpOrForwardTryCache((id)referent, @selector(allowsWeakReference),
                                       referent->getIsa());
            // 如果referent不能够被weak引用,则直接返回nil
            if ((IMP)allowsWeakReference == _objc_msgForward) {
                return nil;
            }
            // 调用referent的SEL_allowsWeakReference方法来判断是否正在被释放
            deallocating =
            ! (*allowsWeakReference)(referent, @selector(allowsWeakReference));
        }

        //deallocating正在析构对象 不能引用 判断是不是需要crash
        if (deallocating) {
            if (deallocatingOptions == CrashIfDeallocating) {
                _objc_fatal("Cannot form weak reference to instance (%p) of "
                            "class %s. It is possible that this object was "
                            "over-released, or is in the process of deallocation.",
                            (void*)referent, object_getClassName((id)referent));
            } else {
                return nil;
            }
        }
    }

    // now remember it and where it is being stored
    //对象既没有在析构中 也可以被弱引用
    weak_entry_t *entry;
    // 在 weak_table中找到referent对应的weak_entry,并将referrer加入到weak_entry中
    // 如果能找到weak_entry,则将referrer插入到weak_entry中
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        append_referrer(entry, referrer);
    } 
    else {
        //找不到 创建一个referrer的entry
        weak_entry_t new_entry(referent, referrer);
        //判断是不是需要扩容
        weak_grow_maybe(weak_table);
        //插入新创建的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指针到相应的entry中时,首先要去判断要进行插入的weak指针的引用对象有没有正在析构,是否可以被弱引用,如果都没有那么开始进行插入。
先判断weak_table中是不是已经存在该对象的弱引用了,如果已经存在了entry(说明之前就被弱引用过了),那么直接调用append_referrer把new_referrer插入到referrers中去。
如果不存在entry(也就是第一次被弱引用),那么使用参数referrer创建一个新的entry,判断是不是需要扩容,将新的entry插入到相应的weak_table中。

这其中使用到方法:
weak_entry_for_referent,根据传入的地址查询并返回相关对象的entry

weak_entry_for_referent

append_referrer,这里有动态数组四分之三扩容的判断

append_referrer

weak_grow_maybe,这个地方是扩容处理,进行weak_resize方法的调用,同样空间过于浪费也会调用weak_compact_maybe缩小空间的优化处理。

扩容和缩容
  • weak_clear_no_lock
    这个方法weak_clear_no_lock是weak指针置为nil的关键。通过注释可以看到,这个方法通过dealloc调用,它的调用过程是object_dispose->objc_destructInstance->clearDeallocating->sidetable_clearDeallocating或者是clearDeallocating_slow,而方法sidetable_clearDeallocating和方法clearDeallocating_slow中都通过判断是不是有弱引用进而调用weak_clear_no_lock方法。

我们接下来看看具体的实现:


/** 
 * Called by dealloc; nils out all weak pointers that point to the 
 * provided object so that they can no longer be used.
 * 
 * @param weak_table 
 * @param referent The object being deallocated. 
 */
//对象释放 清除相关weak指针,移除相关的weak表
void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
    //拿到被销毁的对象的指针
    objc_object *referent = (objc_object *)referent_id;

    //从weak_table中拿到相关的entry entry中的referrers有弱引用当前对象的所有weak指针
    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
    if (entry == nil) {
        /// XXX shouldn't happen, but does with mismatched CF/objc
        //printf("XXX no entry for clear deallocating %p\n", referent);
        return;
    }

    // zero out references
    weak_referrer_t *referrers;
    size_t count;
    //使用的是动态数组
    if (entry->out_of_line()) {
        //拿到相关的弱引用指针表
        referrers = entry->referrers;
        //拿到引用数量
        count = TABLE_SIZE(entry);
    } 
    else {
        //没使用动态数组 没超过四个弱引用 直接去处理inline_referrers
        //WEAK_INLINE_COUNT 等于 4
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }
    
    //开始遍历
    for (size_t i = 0; i < count; ++i) {
        //**referrer 存的就是被弱引用的指针地址 指针里指向的内容装的还是一个指针
        objc_object **referrer = referrers[i];
        // 如果weak ptr确实weak引用了referent,则将weak ptr设置为nil,这也就是为什么weak 指针会自动设置为nil的原因
        if (referrer) {
            if (*referrer == referent) {
                *referrer = nil;
            }
            else if (*referrer) {
                _objc_inform("__weak variable at %p holds %p instead of %p. "
                             "This is probably incorrect use of "
                             "objc_storeWeak() and objc_loadWeak(). "
                             "Break on objc_weak_error to debug.\n", 
                             referrer, (void*)*referrer, (void*)referent);
                objc_weak_error();
            }
        }
    }
    //判断移除完相关指针后表是不是空的 需不需要移除 需不需要缩容
    weak_entry_remove(weak_table, entry);
}

总结

通过上面的结构分析,源码分析,了解了iOS内存管理中weak表的操作,增容,缩容,插入删除等等一系列增删改查的操作,可以了解到,weak表的操作都是一层一层进行的,优先是inline固定长度为4的数组,其次是动态数组。

  • 删除的时候,先去在固定长度中进行查找删除,没找到再去查动态数组,删除成功再去判断表删除完是不是为空了需不要要移除表,需不需要缩容;

  • 增加新的弱引用时亦是如此,先去判断inline中有没有位置,有位置直接插入,没位置了开启动态数组,通过index后移的方法查找没有存东西的位置进行插入,插入完去判断需要扩容嘛。

👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻
整个过程是非常严谨的,层层递进~
👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻👍🏻

weak表告一段落,看了几天也只是了解些皮毛,以后有机会再了解weak相关知识时希望可以更加的深入了解~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容