The weak pointer in OC is mainly used to break loops or prevent circular references, and is widely used in many scenarios. So what is the underlying operation mechanism of the pointer modified by weak and the object pointed to? Why does the weak pointer automatically set to nil when the object is released and destroyed, thus avoiding the wild pointer error?

Weak pointer implementation principle

When an object is referenced by an weak pointer, the underlying implementation principle is: the weak pointer is not retained but is marked and associated with the referenced object using a hash table. When the object is destroyed and the memory tube is released, the address of the weak pointer is searched through the previous flag, and the weak pointer is set to nil.

Weak pointer to achieve source analysis

First, the order of function calls implemented by weak is shown below

weak

The analysis of SideTable and finding the corresponding SideTable based on the object pointer was explained in the previous article on object reference counting, which will not be repeated here. Here we mainly analyze the function of weak_entry_t structure

id 
weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, bool crashIfDeallocating)
{
     // now remember it and where it is being stored
    weak_entry_t *entry;
    if ((entry = weak_entry_for_referent(weak_table, referent))) { //ifCheck whether the weak pointer to the object already has a corresponding entry. If so, it indicates that the object was previously referred to as append_referrer(entry, referrer). // insert the referrer into the corresponding entry}elseWeak_entry_t new_entry(referent, referrer); // The object is referenced by a weak pointer for the first time. weak_grow_maybe(weak_table); Weak_entry_insert (weak_table, &new_entry); // Weak_table. Weak_entries expansion. Weak_entry_t put weak_table. Weak_entries in weak_table. Weak_entries array, get index by hash &mask of referent} // Do notset *referrer. objc_storeWeak() requires that the 
    // value not change.

    return referent_id;
}
Copy the code

The logic here is mainly by judging whether the corresponding Weak_entry_t structure is included in the Weak_table attribute of SideTable. The realization of search is mainly through the object pointer after a certain hash algorithm operation with a specific value (the value is usually to find the largest index of the order table) after the index, and then according to the index to find a specific value and the value to find the comparison results. Specific logic can be seen from the source weak_entry_for_referent the implementation of the function.

The first time an object is referred to by a weak reference

The else statement above deals with the first time the object is referred to by a weak reference. After generating a Weak_entry_t structure, insert it into weak_table. The logic of insertion is the same as that of determining whether there is a specific Weak_entry_t above. It is obtained by hash algorithm, and then insert the corresponding index position. There is also a treatment for enlarging weak_table weak_grow_maybe(weak_table); It is mainly to judge that the capacity of the object where the Weak_table is stored is greater than or equal to 3/4 of the total capacity. Expand the capacity of the weak_table twice, and then copy the old data to the expanded memory.

The object has already been referred to by a weak reference

This is a bit more complicated. First let’s look at the structure definition that ultimately holds the address of the weak pointer

Struct Weak_entry_t {// Referenced object pointer (after wrapper processing) 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; //8 bytes uintptr_t max_hash_displacement; / / 8 bytes}; struct { // out_of_line_ness field is low bits of inline_referrers[1] weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]; // 32 bytes}; }; }Copy the code

Weak_entry_t structure mainly stores the weak pointer address of the object through the union above. The union in the code is divided into two parts, and the memory size is 64 bytes. When adding the weak pointer, the inline_referrers array is preferentially used for storage. If the array is already full of data (4 Pointers), the array pointer of the above structure will be used to create space on the heap to store data. This allows more weak pointer addresses to be stored.

Weak matrix automatically set nil principle

Weak_clear_no_lock if the object is pointed by an weak pointer, the weak_clear_NO_lock method will be executed according to the call order of the function when the -dealloc method is executed when the object is destroyed and the memory is released. So let’s see how do we make weakPointer = nil next time

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

    weak_referrer_t *referrers;
    size_t count;
    
    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];
        if (referrer) {
            if (*referrer == referent) {
                *referrer = nil;
            }
            }
        }
    }
    
    weak_entry_remove(weak_table, entry);
}

Copy the code

It can be seen that the above code mainly obtains weak_entry_t *entry of the corresponding object, obtains the number of weak pointer addresses stored according to its internal structure, and then traverses the memory of the stored weak pointer and sets the weak pointer to nil.