本文介绍: Weak表示弱引用,用weak修饰描述引用对象计数器并不会增加,并且weak指针引用对象被释放时自动置为nil可以解决循环引用问题。那么weak的具体实现原理如何呢?

Weak基本用法

Weak表示弱引用,用weak修饰描述引用对象计数器并不会增加,并且weak指针引用对象被释放时自动置为nil可以解决循环引用问题
那么weak的具体实现原理如何呢?

Weak实现原理

iOS是如何实现weak的呢,其实weak底层一个hash表,key是所指向对象指针valueweak指针地址数组(因为一个对象可能被多个弱引用指针指向)。

Runtime维护了一张weak表,用来存储某个对象的所有weak指针,

之前探究ARC的时候探究过ARC中weak的底层汇编
在这里插入图片描述
可以看见weak的一整个周期就是从objc_initWeak开始,等到对象释放后,调用objc_destroyWeak销毁指针。

那么首先就从这个入口 objc_initWeak开始看。

objc_initWeak,objc_destroyWeak

objc库找一下源码

id
objc_initWeak(id *location, id newObj)
{
    // newObj 是weak对象地址
    if (!newObj) {
        *location = nil;
        return nil;
    }

    // 调用storeWeak函数 传入模板参数:haveOld = false, haveNew = true, 
    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}

可以看见底层是由storeWeak来实现逻辑的,我们了解一下他的模板

storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating&gt; (location, (objc_object*)newObj);

其中DontHaveOld、DoHaveNew和DoCrashIfDeallocating都是模板参数,具体含义如下

enum HaveOld { DontHaveOld = false, DoHaveOld = true }; // 是否有值
enum HaveNew { DontHaveNew = false, DoHaveNew = true }; // 是否有新值
enum CrashIfDeallocating {
    DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
}; // 操作正在释放中的对象是否 Crash
static id 
storeWeak(id *location, objc_object *newObj)
{
    // location是weak指针,newObj是weak将要指向的对象
    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取出存储弱引用表SideTable(weak_table_t的一层封装
    if (haveOld) {
        // 获得索引为oldObj存储的值
        oldObj = *location;
        oldTable = &amp;SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (haveNew) {
        // 更改新值指针,获得以newObj为索引存储的值
        newTable = &amp;SideTables()[newObj];
    } else {
        newTable = nil;
    }
// 加锁操作,防止多线程竞争冲突
    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
// 再次判断location是否和oldObj相等
    // 如果不相等说明当前location已经处理过oldObj,但是被其他线程修改
    if (haveOld  &amp;&amp;  *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.
    // 防止弱引用间死锁
    // 并且通过+initialize初始化构造器保证所有弱引用的isa非空指向
    if (haveNew  &amp;&amp;  newObj) {
        // 获取新对象的isa指针
        Class cls = newObj->getIsa();
        // 如果该对象类还未进行初始化则进行初始化
        if (cls != previouslyInitializedClass  &amp;&amp;  
            !((objc_class *)cls)->isInitialized()) 
        {
            
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            // 对其isa指针进行初始化
            _class_initialize(_class_getNonMetaClass(cls, (id)newObj));

            //如果该类已经完成执行+initialize方法是最理想情况
            //如果该类+initialize在线程
            //例如+initialize正在调用storeWeak方法
            //需要手动对其增加保护策略,并设置previouslyInitializedClass指针进行标记
        
            
            previouslyInitializedClass = cls;
            // 再次尝试
            goto retry;
        }
    }

    // 如果存在旧值,清除旧值对应的弱引用表
    // Clean up old value, if any.
    if (haveOld) {
        weak_unregister_no_lock(&amp;oldTable->weak_table, oldObj, location);
    }

    // Assign new value, if any.
    // 如果存在新值,注册新值对应的弱引用表
    if (haveNew) {
        newObj = (objc_object *)
            weak_register_no_lock(&amp;newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating);
        // 如果弱引用被释放的weak_register_no_lock方法返回nil
             //  设置 isa 标志位 weakly_referenced 为 true
        if (newObj  &amp;&amp;  !newObj->isTaggedPointer()) {
            newObj->setWeaklyReferenced_nolock();
        }

        // Do not set *location anywhere else. That would introduce a race.
        // 之前不要设置location对象, 这里需要更改指针指向
        *location = (id)newObj;
    }
    else {
        // 没有新值无需操作
    }
    
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

    return (id)newObj;
}

具体流程如下所示
在这里插入图片描述
大概做了以下几件事:
1.从全局哈希SideTables中,利用对象本身地址进行位运算后得到对应下标,取得该对象的弱引用表。SideTables一个 64 个元素长度的散列表,发生碰撞时,可能一个SideTable存在多个对象共享一个弱引用表。
2.如果有分配新值,则检查新值对应的类是否初始化过,如果没有,则就地初始化。
3.如果 location 有指向其他旧值,则将旧值对应的弱引用表进行注销。
4.如果分配了新值,将新值注册到对应的弱引用表中。将isa.weakly_referenced设置true,表示该对象是有弱引用变量,释放时要去清空弱引用表。

看到这里可能还是不能理解这一段代码,其中出现了几个新的概念,sideTable
,weak_register_no_lock 和 weak_unregister_no_lock,下面具体解析一下它们的底层原理

SideTable

为了管理所有对象的引用计数weak指针苹果创建一个全局SideTables,虽然名字后面又个”s”,但其不过还是一个全局Hash桶,里面内容装的都是SideTable结构体而已。它使用对象的内存地址当它的key。来管理引用计数weak指针。
之前在探究ARC究竟做了什么的时候,在保存引用计数这里提到过,如果引用计数发生上溢下溢,会将引用计数存在SideTable类中今天就来看看这个类内部实现。查看其源码

// MARK: - sideTable类
struct SideTable {
    spinlock_t slock;//保证原子操作的自旋锁
    RefcountMap refcnts;//保存引用计数的散列表
    weak_table_t weak_table;//保存weak引用的全局列表

    SideTable() {
        memset(&amp;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 slock

这个成员变量是一个自旋锁,保证同一时间内只有一个线程访问共享资源,如果共享数据已经有其他线程加锁了,线程会以死循环方式等待锁,一旦被访问资源解锁,则等待资源的线程会立即执行这里主要用来保证操作引用计数不会造成线程竞争而产生错误

RefcountMap

这个成员变量就是保存引用计数的散列表。看一下下面这张图。
在这里插入图片描述

结合它的定义typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap;
可以看出来这个散列表的key是对象的地址,value是引用计数
当value为0时,RefcountMap自动清除这条记录
每个RefcountMap中存储一堆对象的引用计数。

那么查询引用计数的流程是什么呢?

  1. 通过计算对象地址哈希值, 来从 SideTables 中获取对应的 SideTable. 哈希重复的对象的引用计数存储在同一个 SideTable 里.
  2. SideTable 使用 find() 方法和重载 [] 运算符方式, 通过对象地址来确定对象对应的桶. 最终执行到的查找算法是 LookupBucketFor()。这个涉及到了OC底层的存储,之前在学习关联对象的时候,遇见过这个函数,不过关联对象偏向于使用InsertIntoBucket完成工作。 下面分析分析源码。

LookupBucketFor()

value_type&amp; FindAndConstruct(const KeyT &amp;Key) {
    BucketT *TheBucket;
    if (LookupBucketFor(Key, TheBucket))
      return *TheBucket;
    return *InsertIntoBucket(Key, ValueT(), TheBucket);
  }
  // 找到了就返回这个对象的桶
  // 没有找到则进行插入操作。
// LookupBucketFor - Lookup the appropriate bucket for Val, returning it in
/// FoundBucket.  If the bucket contains the key and a value, this returns
/// true, otherwise it returns a bucket with an empty marker or tombstone and
/// returns false.
// 为Val查找相应的桶,在/// FoundBucket中返回。如果桶中包含键和值,则返回/// true,否则返回带有空标记或墓碑的桶,并返回false
template<typename LookupKeyT>
bool LookupBucketFor(const LookupKeyT &amp;Val,
                     const BucketT *&amp;FoundBucket) const {
  // 获取buckets的首地址
  const BucketT *BucketsPtr = getBuckets();
  // 获取可存储的buckets的总数
  const unsigned NumBuckets = getNumBuckets();

  if (NumBuckets == 0) {
    // 如果NumBuckets = 0 返回 false
    FoundBucket = nullptr;
    return false;
  }

  // FoundTombstone - Keep track of whether we find a tombstone while probing.
  const BucketT *FoundTombstone = nullptr;
  const KeyT EmptyKey = getEmptyKey();
  const KeyT TombstoneKey = getTombstoneKey();
  assert(!KeyInfoT::isEqual(Val, EmptyKey) &&
         !KeyInfoT::isEqual(Val, TombstoneKey) &&
         "Empty/Tombstone value shouldn't be inserted into map!");
  // 计算hash下标
  unsigned BucketNo = getHashValue(Val) & (NumBuckets-1);
  unsigned ProbeAmt = 1;
  while (true) {
    // 内存平移:找到hash下标对应的Bucket
    const BucketT *ThisBucket = BucketsPtr + BucketNo;
    // Found Val's bucket?  If so, return it.
    if (LLVM_LIKELY(KeyInfoT::isEqual(Val, ThisBucket->getFirst()))) {
      // 如果查询到`Bucket`的`key`和`Val`相等 返回当前的Bucket说明查询到了
      FoundBucket = ThisBucket;
      return true;
    }

    // If we found an empty bucket, the key doesn't exist in the set.
    // Insert it and return the default value.
    // 如果bucket为空桶,表示可以插入
    if (LLVM_LIKELY(KeyInfoT::isEqual(ThisBucket->getFirst(), EmptyKey))) {
      // 如果曾遇到墓碑, 则使用墓碑的位置
      // of the empty bucket we eventually probed to.
      FoundBucket = FoundTombstone ? FoundTombstone : ThisBucket;
      return false;//找到空占位符, 则表明表中没有已经插入了该对象的桶
    }

    // If this is a tombstone, remember it.  If Val ends up not in the map, we
    // prefer to return it than something that would require more probing.
    // Ditto for zero values.
    //如果找到了墓碑
    if (KeyInfoT::isEqual(ThisBucket->getFirst(), TombstoneKey) &&
        !FoundTombstone)
      FoundTombstone = ThisBucket;  //记录墓碑的位置
    if (ValueInfoT::isPurgeable(ThisBucket->getSecond())  &&  !FoundTombstone)
    //这里涉及到最初定义 typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap, 传入的第三参数 true
    //这个参数代表是否可以清除 0 值, 也就是说这个参数为 true 并且没有墓碑的时候, 会记录下找到的 value 为 0 的桶
      FoundTombstone = ThisBucket;

    // Otherwise, it's a hash collision or a tombstone, continue quadratic
     //用于计数的 ProbeAmt 如果大于了数组容量, 就会抛出异常
    if (ProbeAmt > NumBuckets) {
      FatalCorruptHashTables(BucketsPtr, NumBuckets);
    }
    // 重新计算hash下标
    BucketNo += ProbeAmt++;
    BucketNo &= (NumBuckets-1);
  }
}


查找算法会先对桶的个数进行判断, 如果桶数为 0 则 return false 回上一级调用插入方法. 如果查找算法找到空桶或者墓碑桶, 同样 return false 回上一级调用插入算法, 不过会先记录下找到的桶. 如果找到了对象对应的桶, 只需要对其引用计数 + 1 或者 – 1. 如果引用计数为 0 需要销毁对象, 就将这个桶中的 key 设置为 TombstoneKey,这样可以避免后续给hash相同的对象增加引用计数时,在销毁后的位置插入了桶。

weak_table_t weak_table

储存对象弱引用指针的hash表。weak功能实现的核心数据结构

struct weak_table_t {
    weak_entry_t *weak_entries;  //连续地址空间的头指针, 数组
    //管理所有指向某对象的weak指针,也是一个hash
    size_t    num_entries;  //数组中已占用位置的个数
    uintptr_t mask;  //数组下标最大值(即数组大小 -1)
    uintptr_t max_hash_displacement;  //最大哈希偏移
};


weak中解除和链接弱引用的实现

首先在之前initWeak里面提到了weak_register_no_lock,这个犯法是用来链接弱引用表的,指向对象注册在弱引用表中
看看源码:

id 
weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, bool crashIfDeallocating)

首先可以看见四个参数:

id 
weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, bool crashIfDeallocating)
{
    objc_object *referent = (objc_object *)referent_id;// 被引用的对象
    objc_object **referrer = (objc_object **)referrer_id;// 弱引用变量

    if (!referent  ||  referent->isTaggedPointer()) return referent_id;

    // 确保弱引用对象是否可行
    // ensure that the referenced object is viable
    bool deallocating;
    if (!referent->ISA()->hasCustomRR()) {
        deallocating = referent->rootIsDeallocating();
    }
    else {
        BOOL (*allowsWeakReference)(objc_object *, SEL) = 
            (BOOL(*)(objc_object *, SEL))
            object_getMethodImplementation((id)referent, 
                                           SEL_allowsWeakReference);
        if ((IMP)allowsWeakReference == _objc_msgForward) {
            return nil;
        }
        deallocating =
            ! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
    }
    // 如果正在释放中,则根据 crashIfDeallocating 判断是否触发 crash
    if (deallocating) {
        if (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 表中对应记录
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        append_referrer(entry, referrer);
    } 
    else {
        
        // 没有在 weak 表中找到对应记录,则新建一个记录
        weak_entry_t new_entry(referent, referrer);
        
        // 查看是否需要扩容
        weak_grow_maybe(weak_table);
        
        // 将记录插入 weak 表中
        weak_entry_insert(weak_table, &new_entry);
    }

    // Do not set *referrer. objc_storeWeak() requires that the 
    // value not change.

    return referent_id;
}

上述代码主要功能如下

  • 判断被指向对象是否可行,也就是判断其是否正在释放,并且会根据crashIfDeallocating判断是否触发crash
  • weak_table检测是否有被指向对象的entry,如果有的话,直接将该弱引用变量指针加入到该entry
  • 如果没有找到对应的entry,新建一个entry,并将弱引用变量指针地址加入entry,同时检查weaktable是否扩容。

从上可以看出链接弱引用主要利用到了entry,可以联想到,解除也是利用这个。下面看看解除引用的具体实现。

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表中查找
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        
        // 找到相应记录后,将该引用从记录中移除
        remove_referrer(entry, referrer);
        // 移除检查该记录是否为空
        bool empty = true;
        if (entry->out_of_line()  &&  entry->num_refs != 0) {
            // 不为空标记记录为false
            empty = false;
        }
        else {
            // 对比到记录的每一行
            for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
                if (entry->inline_referrers[i]) {
                    empty = false; 
                    break;
                }
            }
        }
        // 如果当前记录为空移除记录

        if (empty) {
            weak_entry_remove(weak_table, entry);
        }
    }

    // Do not set *referrer = nil. objc_storeWeak() requires that the 
    // value not change.
}

这段代码的主要流程

  • weak_table中根据找到被引用对象对应的entry然后将弱引用变量指针referrerentry移除
  • 移除弱引用变量指针referrer之后,检查entry是否为空,如果为空将其从weak_table移除

weak_table

上面提到了一个全局表weak_table,用来保存每一个对象的entry,下面看看具体实现。

static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
    assert(referent);
    // 获取weak_table中存储所有对象entry的数组
    weak_entry_t *weak_entries = weak_table->weak_entries;
    // 没有直接返回nil
    if (!weak_entries) return nil;
    // hash_pointer 对地址做位运算得出哈希表下标的方式
    size_t begin = hash_pointer(referent) & weak_table->mask;
    size_t index = begin;
    size_t hash_displacement = 0;
    
    // 遍历weak_table中的weak_entries,比对weak_entries[index].referent对象和referent对象是否相等
    while (weak_table->weak_entries[index].referent != referent) {
        index = (index+1) & weak_table->mask;// 不能超过 weak_table 最大长度限制
        // 回到初始下标,异常报错
        if (index == begin) bad_weak_table(weak_table->weak_entries);
        hash_displacement++;
        // 以及达到最大hash值,说明遍历完毕没找到
        if (hash_displacement > weak_table->max_hash_displacement) {
            return nil;
        }
    }
    
    // 返回weak指针
    return &weak_table->weak_entries[index];
}

上面代码的流程:

  • 通过被引用对象地址计算获得哈希表下标。
  • 检查对应下标存储的是不是我们要找到地址,如果是则返回该地址。
  • 如果不是则继续往下找(线性查找),直至找到。在下移的过程中,下标不能超过weak_table最大长度,同时hash_displacement不能超过记录的max_hash_displacement最大哈希位移max_hash_displacement是所有插入操作时记录的最大哈希位移,如果超过了,抛出异常

下面看看如何插入entry

static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)
{
    weak_entry_t *weak_entries = weak_table->weak_entries;
    assert(weak_entries != nil);
    // 通过哈希算法得到下标
    size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask);
    size_t index = begin;
    size_t hash_displacement = 0;
    // 判断当前下标是否为空,如果不是继续往下寻址空位
    while (weak_entries[index].referent != nil) {
        index = (index+1) & weak_table->mask;
        if (index == begin) bad_weak_table(weak_entries);
        hash_displacement++;
    }
    // 找到空位后存入
    weak_entries[index] = *new_entry;
    weak_table->num_entries++;
    // 更新最大哈希位移
    if (hash_displacement > weak_table->max_hash_displacement) {
        weak_table->max_hash_displacement = hash_displacement;
    }
}

看来之前的过程就很容易理解这个插入了,首先根据对象地址计算得到hash值,判断该位置是否为空,不为空往下找,直到找到空位置,将弱引用变量指针存入空位,同时更新weak_table当前成员数量num_entries和最大哈希位移max_hash_displacement

下面看看移除entry的代码

static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
{
    // 释放 entry 中的所有弱引用
    if (entry->out_of_line()) free(entry->referrers);
    // 置空指针
    bzero(entry, sizeof(*entry));
	// 更新 weak_table 对象数量,并检查是否可以缩减表容量
    weak_table->num_entries--;
    weak_compact_maybe(weak_table);
}
  • 释放entry和其中的弱引用变量。
  • 更新 weak_table 对象数量,并检查是否可以缩减表容量

entry 和 referrer

entry以及比较熟悉了,一个对象的弱引用记录,referrer则是代表弱引用变量,每次被弱引用时,都会将弱引用变量指针referrer加入entry中,而当原对象被释放时,会将entry清空移除
看看entry的定义

// MARK: - weak_entry_t
struct weak_entry_t {
    DisguisedPtr<objc_object> referent;// 对象的地址
    union {
        struct {
            weak_referrer_t *referrers;//可变数组,里面保存着所有指向这个对象的弱引用的地址。当这个对象被释放的时候,referrers里的所有指针都会被设置成nil。
            //指向 referent 对象的 weak 指针数组
            uintptr_t        out_of_line_ness : 2;// 这里标记是否超过内连边界
            uintptr_t        num_refs : PTR_MINUS_2;// 数组已占大小
            uintptr_t        mask;// 数组大小
            uintptr_t        max_hash_displacement;// 最大hash偏移
        };
        struct {
            // out_of_line_ness field is low bits of inline_referrers[1]
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT]; //只有4个元素的数组,默认情况下用它来存储弱引用的指针。当大于4个的时候使用referrers来存储指针。
            //当指向这个对象的weak指针不超过4个,则直接使用数组inline_referrers
        };
    };

可以看见苹果这里为了避免数量少使用hash造成性能浪费,使用共用体,做了一个判断,如果weak指针数量小于4就使用inline_referrers[WEAK_INLINE_COUNT]
下面看看entry是如何添加referrer的:

// MARK: - 将weak指针添加到entry的weak指针集合
/** 
 * Add the given referrer to set of weak pointers in this entry.
 * Does not perform duplicate checking (b/c weak pointers are never
 * added to a set twice). 
 *
 * @param entry The entry holding the set of weak pointers. 
 * @param new_referrer The new weak pointer to be added.
 */
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
    if (! entry->out_of_line()) {
        // 没有超出范围,直接加入到对应位置中
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            if (entry->inline_referrers[i] == nil) {
                entry->inline_referrers[i] = new_referrer;
                return;
            }
        }

        // Couldn't insert inline. Allocate out of line.
        // // 如果 inline_referrers 超出 WEAK_INLINE_COUNT 数量,则执行下面代码
        // 开辟新空间
        weak_referrer_t *new_referrers = (weak_referrer_t *)
            calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
        // This constructed table is invalid, but grow_refs_and_insert
        // 将原来的引用转移到新空间
        
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            new_referrers[i] = entry->inline_referrers[i];
        }
        // 修改 entry 内容及标志位
        entry->referrers = new_referrers;
        entry->num_refs = WEAK_INLINE_COUNT;
        entry->out_of_line_ness = REFERRERS_OUT_OF_LINE;
        entry->mask = WEAK_INLINE_COUNT-1;
        entry->max_hash_displacement = 0;
    }

    assert(entry->out_of_line());
    // 当负载因子过高进行扩容
    if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
        return grow_refs_and_insert(entry, new_referrer);
    }
    //  // 根据地址计算下标
    size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
    size_t index = begin;
    size_t hash_displacement = 0;
    // 该下表位置下不为空,发生 hash 冲突,
    while (entry->referrers[index] != nil) {
        hash_displacement++;
        // 线性移动
        index = (index+1) & entry->mask;
        // 异常
        if (index == begin) bad_weak_table(entry);
    }
    // // 记录最大位移
    if (hash_displacement > entry->max_hash_displacement) {
        entry->max_hash_displacement = hash_displacement;
    }
    // // 找到合适下标后存储
    weak_referrer_t &ref = entry->referrers[index];
    ref = new_referrer;
    entry->num_refs++;
}

entry结构weak_table相似,都使用了哈希表,并且使用线性探测法寻找对应位置。在此基础上有一点不同的地方:

  • entry有一个标志位out_of_line,最初时该标志位为falseentry使用的是一个有序数组inline_referrers的存储结构
  • 当inline_referrers的成员数量超过WEAK_INLINE_COUNTout_of_line标志位变成true,开始使用哈希表存储结构。每当哈希表负载超过 3/4 时会进行扩容。
    下面看看如何去掉weak指针:
static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
    if (! entry->out_of_line()) {
        // 未超出 inline_referrers 时直接将对应位置清空
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            if (entry->inline_referrers[i] == old_referrer) {
                entry->inline_referrers[i] = nil;
                return;
            }
        }
        _objc_inform("Attempted to unregister unknown __weak variable "
                     "at %p. This is probably incorrect use of "
                     "objc_storeWeak() and objc_loadWeak(). "
                     "Break on objc_weak_error to debug.n", 
                     old_referrer);
        objc_weak_error();
        return;
    }
    // 超出 inline_referrers 的逻辑,也就是weak指针大于4 
    // 计算下标
    size_t begin = w_hash_pointer(old_referrer) & (entry->mask);
    size_t index = begin;
    size_t hash_displacement = 0;
    // 发生哈希冲突继续往后查找
    while (entry->referrers[index] != old_referrer) {
        index = (index+1) & entry->mask;
        if (index == begin) bad_weak_table(entry);
        hash_displacement++;
        // 越界抛出异常
        if (hash_displacement > entry->max_hash_displacement) {
            _objc_inform("Attempted to unregister unknown __weak variable "
                         "at %p. This is probably incorrect use of "
                         "objc_storeWeak() and objc_loadWeak(). "
                         "Break on objc_weak_error to debug.n", 
                         old_referrer);
            objc_weak_error();
            return;
        }
    }
    // 找到对应位置后置为nil
    entry->referrers[index] = nil;
    // entry存的weak元素数量减1
    entry->num_refs--;
}

从entry移除referrer的步骤

  • out_of_line为false时,从有序数组inline_referrers中查找并移除。
  • out_of_line为true时,从哈希表中查找并移除

dealloc

当被引用的对象被释放后,会去检查isa.weakly_referenced标志位,每个被弱引用的对象weakly_referenced标志位都为true。

NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
    assert(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));
    // 根据指针获取对应 Sidetable
    SideTable& table = SideTables()[this];
    table.lock();
    if (isa.weakly_referenced) {
        // 存在弱引用
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    if (isa.has_sidetable_rc) {
        table.refcnts.erase(this);
    }
    table.unlock();
}

从上面的代码可以看出,在对象释执行dealloc函数时,会检查isa.weakly_referenced标志位,然后判断是否要清理weak_table中的entry

这最后还是走到了前面remove。

以上就是weak的实现原理

原文地址:https://blog.csdn.net/chabuduoxs/article/details/126000448

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_18227.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注