In the development process of iOS, a modifier weak is often used. The usage scenario is clear to everyone, so as to avoid the problem that strong references between objects can not be released normally and eventually lead to memory leakage. The weak keyword is used for weak references. The counter of the referenced object is not incremented by 1 and is automatically set to nil when the referenced object is released.

1. Weak

The following snippet of code is a common use of weak in development

Person *object = [Person alloc];
id __weak objc = object;
Copy the code

If you trace assembly information at this interrupt point, you can see that the underlying library is tunedobjc_initWeakfunction

So what does the code for the objc_initWeak method look like?

1, objc_initWeak method

Here is the underlying source code for the objc_initWeak method

id objc_initWeak(id *location, id newObj)
{
    if(! newObj) { *location = nil;return nil;
    }

    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}
Copy the code

The method’s two parameters are location and newObj.

  • Location: The address of the __weak pointer that stores the pointer so that the object it points to can be set to nil at the end.
  • NewObj: The object referenced. Obj in this example.

As you can see from the above code, the objc_initWeak method is just an entry point to a deeper function call, inside which the storeWeak method is called. Let’s look at the implementation code for the storeWeak method.

2. StoreWeak method

The following is the implementation code for the storeWeak method.

// Template parameters.
enum HaveOld { DontHaveOld = false, DoHaveOld = true };
enum HaveNew { DontHaveNew = false, DoHaveNew = true };
enum CrashIfDeallocating {
    DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
};

template <HaveOld haveOld, HaveNew haveNew,
          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:
    if (haveOld) { // If weak PTR has a weak reference to obj before, remove the SideTable corresponding to obj and assign it to oldTable
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil; // oldTable = nil if weak PTR has not previously weakly referenced an obj
    }
    if (haveNew) { // If weak PTR wants weak to refer to a new obj, the SideTable corresponding to obj is removed and assigned to newTable
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil; // If weak PTR does not need to reference a new obj, newTable = nil
    }
    
    // Lock operation to prevent multi-thread contention
    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);

    // Location should be consistent with oldObj. If it is different, the current location has already been processed by oldObj but has been modified by another thread
    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();
        if(cls ! = previouslyInitializedClass && ! ((objc_class *)cls)->isInitialized())// If CLS is not already initialized, initialize it first and then try setting weak
        {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            _class_initialize(_class_getNonMetaClass(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.
            previouslyInitializedClass = cls; / / the record previouslyInitializedClass here, to prevent the change if branch to enter again

            goto retry; // Get newObj again, newObj should already be initialized}}// Clean up old value, if any.
    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); // If weak_ptr weakly referenced other object oldObj before, weak_unregister_no_lock is called, and the weak_ptr address is removed from weak_entry_t of oldObj
    }

    // Assign new value, if any.
    if (haveNew) { // If weak_ptr needs to weakly reference the new object newObj
        Weak_register_no_lock is called, and weak_ptr's address is recorded in weak_entry_t corresponding to newObj
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
                                  crashIfDeallocating);
        // weak_register_no_lock returns nil if weak store should be rejected
        
        (2) Update weakly_referenced bit flag bit of Isa of newObj
        // Set is-weakly-referenced bit in refcount table.
        if(newObj && ! newObj->isTaggedPointer()) { newObj->setWeaklyReferenced_nolock(); }// Do not set *location anywhere else. That would introduce a race.
        // (3) * assign weak PTR to newObj. As you can see, newObj's reference count is not +1
        *location = (id)newObj; // Set weak PTR to object
    }
    else {
        // No new value. The storage is not changed.
    }
    
    Other threads can access oldTable and newTable
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

    return (id)newObj; // Return newObj, now newObj is inferior to when it was just passed in, Weak-referenced bit position 1
}
Copy the code

The implementation code for the storeWeak method is long, but not hard to understand. Let’s analyze the implementation of this method.

  1. storeWeakThe method actually takes five arguments, respectivelyHaveOld, haveNew and crashIfDeallocating, which are passed in as templates and are three bool arguments. Indicates whether the weak pointer points to a weak reference before, whether the weak pointer needs to point to a new reference, and whether the weakly referenced object should crash if it is being destructed.
  2. The method is maintainedoldTable andnewTableRepresents the old weak reference table and the new weak reference table, respectively, which are bothSideTableThe hash table.
  3. Called if the weak pointer had previously pointed to a weak referenceweak_unregister_no_lock Method to remove the old weak pointer address.
  4. Called if the weak pointer needs to point to a new referenceweak_register_no_lock Method to add the new weak pointer address to the weak reference table.
  5. callsetWeaklyReferenced_nolock The weak () method modifies the bit flag of the newly referenced object

So the focus of this method is weak_unregister_NO_lock and weak_register_no_lock these two methods. Both of these methods operate on a structured variable called the SideTable, so we need to look at the SideTable first.

3, SideTable

Let’s take a look at the definition of SideTable.

struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;
    weak_table_t weak_table;
}
Copy the code

SideTable is clearly defined and has three members:

  • Spinlock_t slock: spinlock used to lock/unlock SideTable.
  • RefcountMap refcnts: used to store reference counts for OC objectsThe hash table(This is only used if isa optimization is not turned on or the isa_T reference count overflows under ISA optimization).
  • weak_table_t weak_table: stores weak reference Pointers to objectsThe hash table. Is the core data structure of the weak function in OC.

3.1, weak_table_t

Let’s take a look at the underlying code of Weak_table_t.

struct weak_table_t {
    weak_entry_t *weak_entries;
    size_t    num_entries;
    uintptr_t mask;
    uintptr_t max_hash_displacement;
};
Copy the code
  • Weak_entries: Hash array, used to store information about weak reference objects. Weak_entry_t
  • Num_entries: number of hash array elements
  • Mask: the hash array length is -1, which will participate in hash calculation. Note that this is the length of the hash array, not the number of elements. For example, the array might be 64 in length and only have 2 elements.)
  • Max_hash_displacement: Maximum number of hash conflicts that can occur to determine if a logical error has occurred (the number of conflicts in the hash table will never exceed the changed value)

Weak_table_t is a typical hash structure. Weak_entries are a dynamic array used to store weak_entry_t elements, which are essentially weak references to OC objects.

3.2, weak_entry_t

Weak_entry_t structure is also a hash structure, and its stored element is the pointer of the weak reference object pointer. By operating the pointer of the pointer, the weak reference pointer can point to nil after object destruction.

#define WEAK_INLINE_COUNT 4
#define REFERRERS_OUT_OF_LINE 2

struct weak_entry_t {
    DisguisedPtr<objc_object> referent; // The weakly referenced object
    
    // List of objects that reference the object, union. If the number of references is less than 4, use the inline_referrers array. When the number is greater than 4, weak_referrer_t *referrers is used
    union {
        struct {
            weak_referrer_t *referrers;                      // A hash array that weakly references the address of the object pointer to the object
            uintptr_t        out_of_line_ness : 2;           // Whether to use dynamic hash array tag bits
            uintptr_t        num_refs : PTR_MINUS_2;         // Number of elements in the hash array
            uintptr_t        mask;                           // The hash array length is -1, which will participate in the hash calculation. Note that this is the length of the hash array, not the number of elements. For example, the array might be 64 in length and contain only 2 elements.
            uintptr_t        max_hash_displacement;          // The maximum number of hash collisions that can occur to determine if a logical error has occurred (the number of hash collisions will never exceed the changed value)
        };
        struct {
            // out_of_line_ness field is low bits of inline_referrers[1]
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };

    bool out_of_line(a) {
        return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
    }

    weak_entry_t& operator= (const weak_entry_t& other) {
        memcpy(this, &other, sizeof(other));
        return *this;
    }

    weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
        : referent(newReferent) Constructor, which initializes a static array
    {
        inline_referrers[0] = newReferrer;
        for (int i = 1; i < WEAK_INLINE_COUNT; i++) { inline_referrers[i] = nil; }}};Copy the code

It can be seen that there is a union in weak_entry_t’s structure definition, There are fixed length array Inline_Referrers [WEAK_INLINE_COUNT] and dynamic array Weak_Referrer_t * Referrers to store the pointer address of weak reference object. Use a function method such as out_of_line() to determine which storage method to use. When the number of weak references to the object is less than or equal to WEAK_INLINE_COUNT, a fixed-length array is used. When WEAK_INLINE_COUNT exceeds WEAK_INLINE_COUNT, the elements in the fixed-length array will be transferred to the dynamic array, and then all the elements will be stored with the dynamic array.

So far we have made it clear that the structure of a weak reference table is a hash table, where Key is the address of the pointed object and Value is an array of addresses of weak Pointers. So let’s see how the weak reference table maintains this data.

4. Weak_register_no_lock method adds weak references

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 the referent is nil or if the referent uses TaggedPointer, it returns nothing
    if(! referent || referent->isTaggedPointer())return referent_id;

    // Make sure the referenced object is available (there is no destructor, and weak references should be supported)
    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);
    }
    // The object being destructed cannot be weakly referenced
    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 {
            returnnil; }}// now remember it and where it is being stored
    // Find the corresponding weak_entry of referent in weak_table, and add the referrer to weak_entry
    weak_entry_t *entry;
    if ((entry = weak_entry_for_referent(weak_table, referent))) { // If weak_entry can be found, insert the referrer into weak_entry
        append_referrer(entry, referrer); 	// Insert the referrer into the weak_entry_t reference array
    } 
    else { // If you can't find it, create a new one
        weak_entry_t new_entry(referent, referrer);  
        weak_grow_maybe(weak_table);
        weak_entry_insert(weak_table, &new_entry);
    }

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

    return referent_id;
}
Copy the code

This method takes four arguments that represent the following:

  • weak_table:weak_table_t Struct type global weak reference table.
  • Referent_id: weak pointer.
  • *referrer_id: weak pointer address.
  • CrashIfDeallocating: Whether the weakly referenced object should crash if it is being destructed.

We can see from the above code that this method mainly does the following convenient work.

  1. If referent is nil or referent is adoptedTaggedPointerCount mode, return directly, do nothing.
  2. Throws an exception if the object is being destructed.
  3. If the object cannot be referenced by weak, return nil.
  4. Called if the object is not redestructed and can be referenced weakweak_entry_for_referent Method finds the corresponding Weak_entry from the weak reference table according to the address of the weak reference object, and calls it if it can find itappend_referrer The weak pointer address is inserted into the. Otherwise, create a Weak_entry.

4.1. Weak_entry_for_referent takes elements

static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
    assert(referent);

    weak_entry_t *weak_entries = weak_table->weak_entries;

    if(! weak_entries)return nil;

    size_t begin = hash_pointer(referent) & weak_table->mask;  Weak_table ->mask is used to ensure that the index does not cross the boundary
    size_t index = begin;
    size_t hash_displacement = 0;
    while(weak_table->weak_entries[index].referent ! = referent) { index = (index+1) & weak_table->mask;
        if (index == begin) bad_weak_table(weak_table->weak_entries); // Triggered a bad weak table crash
        hash_displacement++;
        if (hash_displacement > weak_table->max_hash_displacement) { // When hash conflicts exceed possible Max hash conflicts, the element is not in the hash table, and nil is returned
            returnnil; }}return &weak_table->weak_entries[index];
}
Copy the code

4.2. Append_referrer adds an element

static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
    if (! entry->out_of_line()) { // If Weak_entry does not already use dynamic arrays, go here
        // Try to insert inline.
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            if (entry->inline_referrers[i] == nil) {
                entry->inline_referrers[i] = new_referrer;
                return; }}// If the inline_referrers location is already full, then the inline_referrers will be converted to the dynamic array.
        // Couldn't insert inline. Allocate out of line.
        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
        // will fix it and rehash it.
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            new_referrers[i] = entry->inline_referrers[I];
        }
        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;
    }

    // For dynamic array add-ons:
    assert(entry->out_of_line()); // Assert: the dynamic array must be used at this time

    if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) { // If the number of elements in the dynamic array is greater than or equal to 3/4 of the total array space, then the array space is doubled
        return grow_refs_and_insert(entry, new_referrer); // Expand and insert
    }
    
    // If expansion is not required, directly insert weak_entry into weak_entry
    Weak_entry is a hash table. Key: w_hash_pointer(new_referrer) value: new_referrer
    
    // Careful people may notice that the hash algorithm of weak_entry_t is the same as that of weak_table_t, and the expansion/reduction algorithm is also the same
    size_t begin = w_hash_pointer(new_referrer) & (entry->mask); // '& (entry->mask)' ensures that the begin position can only be greater than or equal to the length of the array
    size_t index = begin;  // The initial hash index
    size_t hash_displacement = 0;  // Is used to record the number of hash collisions, that is, the number of hash reshifts
    while(entry->referrers[index] ! = nil) { hash_displacement++; index = (index+1) & entry->mask;  // index + 1, move to the next position, try again can insert. The value of entry->mask must be 0x111, 0x1111, 0x11111... , because the array grows by *2 each time, i.e. 8, 16, 32, corresponding to the mask of the dynamic array space length -1, which is the previous value.
        if (index == begin) bad_weak_table(entry); // index == begin means that the array has gone around without finding the right place, and there must be something wrong.
    }
    if (hash_displacement > entry->max_hash_displacement) { Max_hash_displacement means that we can try max_hash_displacement at most and be sure to find the hash position of object
        entry->max_hash_displacement = hash_displacement;
    }
    // store ref into hash array and update the number of elements num_refs
    weak_referrer_t &ref = entry->referrers[index];
    ref = new_referrer;
    entry->num_refs++;
}
Copy the code

This code first determines whether to use a fixed-length array or a dynamic array. If a fixed-length array is used, the weak pointer address is simply added to the array. If the fixed-length array is exhausted, the elements of the fixed-length array need to be transferred to the dynamic array.

Weak_unregister_no_lock removes the reference

If the weak pointer previously pointed to a weak reference, the weak_unregister_no_lock method is called to remove the old weak pointer address.

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;

    if ((entry = weak_entry_for_referent(weak_table, referent))) { // Find weak_entry_t corresponding to referent
        remove_referrer(entry, referrer);  // Remove the referrer from the hash array of weak_entry_t corresponding to the referent
       
        // After removing the element, check whether the Weak_entry_t hash array is empty
        bool empty = true;
        if(entry->out_of_line() && entry->num_refs ! =0) {
            empty = false;
        }
        else {
            for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
                if (entry->inline_referrers[i]) {
                    empty = false; 
                    break; }}}if (empty) { // If the hash array of weak_entry_t is empty, you need to remove weak_entry_t from weak_tableweak_entry_remove(weak_table, entry); }}Copy the code
  1. First, it finds the referent’s corresponding Weak_entry_t in the Weak_table
  2. Remove the referrer from weak_entry_t
  3. After removing the element, determine whether there are still elements in weak_entry_t (empty==true?).
  4. If weak_entry_t has no elements at this point, you need to remove weak_entry_t from Weak_table

The reference count does not increase by 1. How do all Pointers to weak references automatically set to nil when the object is released?

6, dealloc

When the reference count of an object is 0, the underlying _objc_rootDealloc method is called to release the object, and the rootDealloc method is called in the _objc_rootDealloc method. The following is the code implementation of the rootDealloc method.

inline void
objc_object::rootDealloc(a)
{
    if (isTaggedPointer()) return;  // fixme necessary?

    if(fastpath(isa.nonpointer && ! isa.weakly_referenced && ! isa.has_assoc && ! isa.has_cxx_dtor && ! isa.has_sidetable_rc)) { assert(! sidetable_present());free(this);
    } 
    else {
        object_dispose((id)this); }}Copy the code
  1. First determine whether the object isTagged PointerIf yes, return directly.
  2. If the object uses the optimized ISA counting mode and the object is not referenced by weak! isa.weakly_referenced, there is no associated object! isa.has_assocThere is no custom C++ destructor! isa.has_cxx_dtorSideTable is not used for reference counting! isa.has_sidetable_rcIs directly released quickly.
  3. If the condition in 2 is not met, it is calledobject_dispose Methods.

6.1, object_dispose

The object_Dispose method is simple and primarily calls the objc_destructInstance method internally.

void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }

    return obj;
}
Copy the code

The above code is clear. If there is a custom C++ destructor, the C++ destructor is called. If there is an associated object, the associated object is removed and itself removed from the Association Manager map. Call the clearDeallocating method to remove the associated reference to the object.

6.2, clearDeallocating

inline void 
objc_object::clearDeallocating(a)
{
    if(slowpath(! isa.nonpointer)) {// Slow path for raw pointer isa.
        sidetable_clearDeallocating();
    }
    else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
        // Slow path for non-pointer isa with weak refs and/or side table data.clearDeallocating_slow(); } assert(! sidetable_present()); }Copy the code

ClearDeallocating has two branches that determine whether the object is optimized for ISA reference counting, or if it isn’t, they need to clean up the reference count data stored in SideTable. Isa.has_sidetable_rc) or weak references (ISa.weakly_referenced). Weakly_referenced Call the clearDeallocating_slow method.

6.3, clearDeallocating_slow

NEVER_INLINE void
objc_object::clearDeallocating_slow(a)
{
    assert(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));

    SideTable& table = SideTables()[this]; // In the global SideTables, use this pointer as the key to find the corresponding SideTable
    table.lock();
    if (isa.weakly_referenced) { // If obj is weakly referenced
        weak_clear_no_lock(&table.weak_table, (id)this); // Clean this in weak_table of SideTable
    }
    if (isa.has_sidetable_rc) { // If SideTable is used for reference counting
        table.refcnts.erase(this); // Remove this from the reference count of SideTable
    }
    table.unlock();
}
Copy the code

What we are interested in here is the Weak_clear_NO_lock method. Weak_clear_no_lock is called here to clean up weak_table.

6.4, weak_clear_no_lock

void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
    objc_object *referent = (objc_object *)referent_id;

    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent); // Find referent's corresponding weak_entry_t in weak_table
    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;
    
    // Find the address array of the weak pointer to the referent and the length of the array
    if (entry->out_of_line()) {
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    } 
    else {
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }
    
    for (size_t i = 0; i < count; ++i) {
        objc_object **referrer = referrers[i]; // Get the address of each weak PTR
        if (referrer) {
            if (*referrer == referent) { // If the weak PTR does refer to the referent, set the weak PTR to nil. This is why the weak pointer is automatically set to nil
                *referrer = nil;
            }
            else if (*referrer) { // If the stored weak PTR does not refer to the weak referent, this may be due to a logic error in the Runtime code
                _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); // Since the referent is being released, the referent's weak_entry_t should also be removed from the weak_table
}
Copy the code

7,

  • 1. The principle of weak is that a hash table of weak_table_t structure is maintained at the bottom level, where key is the address of the object and value is the address array of weak Pointers.
  • The weak keyword is used for weak references. The counter of the referenced object is not incresed by 1 and is automatically set to nil when the referenced object is released.
  • 3. Call when the object is releasedclearDeallocatingThe function retrieves an array of weak pointer addresses based on the address of the object, iterates through the array to set the data to nil, deletes the entry from the weak table, and clears the record of the object.
  • 4. In the article, three structures such as SideTable, Weak_table_t and Weak_entry_t are introduced. The relationship between them is shown in the figure below.

The resources

  • SideTables, SideTable, weak_table, weak_entry_t
  • Weak references the underlying implementation principle