iOS——weak implementation principle

Basic usage of Weak

WeakRepresents a weak reference, with weakdecoration, the counter of the referenced object described will not increase, and weakthe pointer is automatically reset when the referenced object is released nil, which can solve the circular reference problem.
So weakwhat is the specific implementation principle?

Weak implementation principle

How does iOS implement it weak? In fact, weakthe bottom layer is a hashtable, keywhich is a pointer to an object, valueand weakan array of pointer addresses (because an object may be pointed to by multiple weak reference pointers).

RuntimeweakA table is maintained to store all weakpointers of an object,

When I explored ARC before, I explored the underlying compilation of weak in ARC.
insert image description here
It can be seen that the whole cycle of weak starts from objc_initWeak, and after the object is released, call objc_destroyWeak to destroy the pointer.

So first start with this entry objc_initWeak.

objc_initWeak,objc_destroyWeak

Find the source code in the objc library.

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

It can be seen that the underlying logic is implemented by storeWeak, let's take a look at its template.

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

Among them, DontHaveOld, DoHaveNew and DoCrashIfDeallocating are all template parameters, and their specific meanings are as follows:

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 = &SideTables()[oldObj];
    } else {
    
    
        oldTable = nil;
    }
    if (haveNew) {
    
    
        // 更改新值指针,获得以newObj为索引存储的值
        newTable = &SideTables()[newObj];
    } else {
    
    
        newTable = nil;
    }
// 加锁操作,防止多线程中竞争冲突
    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
// 再次判断location是否和oldObj相等
    // 如果不相等说明当前location已经处理过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.
    // 防止弱引用间死锁
    // 并且通过+initialize初始化构造器保证所有弱引用的isa非空指向
    if (haveNew  &&  newObj) {
    
    
        // 获取新对象的isa指针
        Class cls = newObj->getIsa();
        // 如果该对象类还未进行初始化则进行初始化
        if (cls != previouslyInitializedClass  &&  
            !((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(&oldTable->weak_table, oldObj, location);
    }

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

The specific process is as follows:
insert image description here
roughly the following things are done:
1. From the global hash table SideTables, use the address of the object itself to perform bit operations to obtain the corresponding subscript, and obtain the weak reference table of the object. SideTablesIt is a hash table with a length of 64 elements. When a collision occurs, there may SideTablebe multiple objects sharing a weak reference table in one.
2. If there is a new value assigned, check whether the class corresponding to the new value has been initialized, if not, initialize it in place.
3. If locationthere are other old values ​​pointed to, the weak reference table corresponding to the old value will be canceled.
4. If a new value is assigned, register the new value in the corresponding weak reference table. Set isa.weakly_referencedto true, indicating that the object is a weak reference variable, and the weak reference table should be cleared when released.

Seeing this, you may still not understand this piece of code. There are several new concepts, sideTable
, weak_register_no_lock and weak_unregister_no_lock. Let’s analyze their underlying principles in detail.

SideTable

In order to manage the reference counts and weakpointers of all objects , Apple created a global bucket SideTables. Although there is an "s" after the name, it is still a global Hashbucket, and the contents inside are SideTableall structures. It uses the object's memory address as its key. to manage reference counts and weakpointers.
When I was exploring what ARC did before, I mentioned in saving the reference count. If the reference count overflows or underflows, the reference count will be stored in the SideTableclass. Let’s take a look at the internal implementation of this class today. View its source code

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

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

First, you can see that there are three member variables.

spinlock_t slock

This member variable is a spin lock, which ensures that only one thread accesses the shared resource at the same time. If the shared data is already locked by other threads, the thread will wait for the lock in an endless loop. Once the accessed resource is unlocked, then Threads waiting for resources are executed immediately. This is mainly used to ensure that the operation reference count will not cause thread competition and cause errors.

RefcountMap

This member variable is the hash table that holds the reference count. Take a look at the picture below.
insert image description here

Combined with its definition: typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap;
it can be seen that the key of this hash table is the address of the object, and the value is the reference count.
When the value is 0, RefcountMap will automatically clear this record.
Each RefcountMap stores the reference counts of a bunch of objects.

So what is the process of querying reference counts?

  1. Obtain the corresponding SideTable from SideTables by calculating the hash value of the object address. The reference count of the object whose hash value is repeated is stored in the same SideTable.
  2. SideTable uses the find() method and the overloaded [] operator to determine the bucket corresponding to the object through the object address. The final lookup algorithm executed is LookupBucketFor(). This involves the storage at the bottom of OC. I have encountered this function when I was learning associated objects before, but associated objects prefer to use InsertIntoBucket to complete the work. The following analysis analyzes the source code.

LookupBucketFor()

value_type& FindAndConstruct(const KeyT &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 &Val,
                     const BucketT *&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);
  }
}


The search algorithm will first judge the number of buckets. If the number of buckets is 0, return false and call the insertion method at the upper level. If the search algorithm finds an empty bucket or a tombstone bucket, return false and return to the upper level to call the insertion algorithm. The found bucket will be recorded first. If the bucket corresponding to the object is found, you only need to add its reference count + 1 or - 1. If the reference count is 0 and the object needs to be destroyed, set the key in this bucket to TombstoneKey, so that To avoid adding a reference count to an object with the same hash in the future, the bucket is inserted at the position after destruction.

weak_table_t weak_table

A hash table that stores object weak reference pointers. The core data structure implemented by the weak function.

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


Implementation of unlinking and linking weak references in weak

First of all, weak_register_no_lock was mentioned in initWeak before. This crime is used to link the weak reference table, and the pointed object is registered in the weak reference table.
Look at the source code:

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

First, you can see four parameters:

  • @param weak_table a global weak table
  • @param referent The object pointed to by the weak reference
  • @param referrer Weak pointer address
  • Marks whether the last object is being released and caused a crash
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;
}

The main functions of the above code are as follows:

  • Judging whether the pointed object is feasible, that is, judging whether it is being released, and crashIfDeallocatingwhether it will be triggered according to the judgment crash.
  • weak_tableCheck whether there is an object pointed to in , entryand if so, directly add the weak reference variable pointer to entrythe
  • If no corresponding one is found entry, create a new one entry, add the address of the weak reference variable pointer entry, and check weaktablewhether it is expanded.

It can be seen from the above that the link weak reference mainly uses the entry, and it can be imagined that the release also uses this. Let's look at the specific implementation of dereferencing.

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.
}

The main flow of this code

  • Findweak_table the referenced object corresponding to it entry, and then remove the weak reference variable pointer referrerfrom itentry .
  • After removing the weak reference variable pointer referrer, check whether the entry is empty, and remove it weak_tablefrom

weak_table

A global table weak_table is mentioned above, which is used to save the entry of each object. Let's take a look at the specific implementation.

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];
}

The flow of the above code:

  • The subscript of the hash table is obtained by calculating the address of the referenced object.
  • Check whether the corresponding subscript stores the address we want to find, and if so, returns the address.
  • If not, continue to look down (linear search) until you find it. In the process of moving down, the subscript cannot exceed the maximum length of the weak_table, and the hash_displacement cannot exceed the maximum hash displacement of the recorded max_hash_displacement. max_hash_displacement is the maximum hash displacement recorded during all insert operations. If it exceeds, an exception will be thrown.

Let's see how to insert 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;
    }
}

It seems that the previous process is easy to understand this insertion. First, calculate the hash value based on the object address, and judge whether the location is empty. weak_tableThe current number of members num_entriesand the maximum hash shift to update simultaneously max_hash_displacement.

Let's take a look at the code to remove the 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);
}
  • Release the entry and its weak reference variables.
  • Update the number of weak_table objects and check whether the table capacity can be reduced

entry and referrer

entryAnd I am more familiar with it. The weak reference record of an object referrerrepresents a weak reference variable. Every time it is weakly referenced, the weak reference variable pointer will be referreradded entryto it, and when the original object is released, it will be entrycleared and removed.
Look at the definition of 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
        };
    };

It can be seen that in order to avoid performance waste caused by the use of hash in a small number, Apple uses a union and makes a judgment. If the number of weak pointers is less than 4, use it. Let's see howinline_referrers[WEAK_INLINE_COUNT]
entry adds 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++;
}

entryThe structure and weak_tablesimilarity, both use a hash table, and use the linear detection method to find the corresponding position. There is a little difference here:

  • entryThere is a flag out_of_line, initially the flag is false, entryusing an ordered array inline_referrersstorage structure.
  • When the number of members of inline_referrers exceeds WEAK_INLINE_COUNT, out_of_linethe flag bit becomes true, and the hash table storage structure is started. Scaling occurs whenever the hash table load exceeds 3/4.
    Let's see how to remove the weak pointer:
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--;
}

Steps to remove referrer from entry:

  • When out_of_line is false, find and remove from the ordered array inline_referrers.
  • When out_of_line is true, find and remove from the hash table

understanding

When the referenced object is released, isa.weakly_referencedthe flag bit will be checked, and the flag bit of each weakly referenced object weakly_referencedis 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();
}

As can be seen from the above code, when the object executes deallocthe function, it will check isa.weakly_referencedthe flag bit, and then judge whether to clean weak_tableup entry.

This finally came to the front remove.

The above is the implementation principle of weak.

Guess you like

Origin blog.csdn.net/chabuduoxs/article/details/126000448