Weak references are often encountered in development, and there are many articles about them online. This article focuses on the underlying source code implementation.

1.weakBasic usage

Weak is a weak reference. The counter used to describe the referenced object does not increase, and it is automatically set to nil when the referenced object is freed. This avoids the possibility of a wild pointer accessing bad memory and causing a crash.

  • Expansion: Why does the modifier agent use Weak instead of assign?

    Assign can be used to assign a basic data type, and it can also be used to assign an object to OC, but if you assign an object to a strong pointer, when the pointer is freed, it still points to the same piece of memory. You must manually set it to nil, otherwise you will have an wild pointer to the same piece of memory. An EXC_BAD_ACCESS error occurs, calling a memory space that has been freed. Weak can only be used to modify OC objects. It is safer than assign. If the object to which it points disappears, weak is automatically set to nil.

2.weakA summary of the implementation principle

Runtime maintains a weak table that stores all weak Pointers to an object. The weak table is essentially a hash table, where Key is the address of the object to be pointed to, and Value is an array of addresses of weak Pointers whose Value is the address of the object to be pointed to.

  • The implementation of Weak can be summarized in three steps:

    1. Initialization: Runtime calls objc_initWeak, initializing a new weak pointer to the address of the object.

    2. When a reference is added: objc_initWeak calls objc_storeWeak(). Objc_storeWeak () is used to update the pointer to create the corresponding weak reference table.

    3. When released, the clearDeallocating function is called. The clearDeallocating function first gets an array of all weak pointer addresses based on the object’s address, then walks through the array setting the data in it to nil, finally removing the entry from the Weak table, and finally cleaning up the object’s record.

3. Ground floor entrance exploration

We often use weak references during development, using a temporary variable obc1 to store the created variable, as shown in the following code:

    NSObject * objc1 = [[NSObject alloc] init];
    id  __weak objc2 = objc1;
Copy the code

In general, we don’t use __weak to modify a temporary variable that has just been created, because __weak is released as soon as it is created, whereas objc1 is placed in an automatic release pool, delaying the lifetime of the temporary variable. The lifetime of the variable is kept in the pool automatically along with the automatic release, so this ensures that it is not released as soon as it is created.

So what’s the underlying thing doing when you run this code? Add a breakpoint to the second line of code and watch the assembly flow, as shown below:

Run the code to see the assembly process, as shown below:

As you can see from the above assembly process, the initialization of the weak reference is the underlying call to objc_initWeak.

4. Initialize source analysis

From the analysis above, we found the entry, added breakpoints in the entry, and looked at its data through LLDB debugging.

  • Objc_initWeak analysis

    The libobjc.dylib source code successfully locates the initialization process, with objc_initWeak passing two arguments, location being the address of the weak reference (stored in the stack) and newObjc being the object created (stored in the heap). See below:

    First, it checks if the object is empty, and if it is, it returns nil. If it is not empty, the stroeWeak method is called for storage. Location is the address of the weak reference; NewObjc is an object, and the implementation of the underlying object is objc_object.

    Summary: Object must be a valid pointer that is not registered as an __weak object.

  • Data structure analysis

    Before we go into method analysis, we need to get a feel for the data storage structure.

    1. SideTable

      Struct SideTable {// spinlock_t slock; // RefcountMap refcnts; // Weak_table_t weak_table; }Copy the code

      SideTable is a structure whose properties include: spin lock, reference count, and weak reference dependent table.

    2. The weak reference table is weak_TABLE

      Struct weak_table_t {// Save all weak Pointers to specified objects. Weak_entry_t *weak_entries; // Storage space size_t num_entries; // Uintptr_t mask; // Uintptr_t max_hash_displacement; }Copy the code

      Weak table is a weak reference table. It is implemented as a weak_table_T structure, which stores all weak reference information related to an object. It is a hash table. Use the address of an indefinite type object as the key, and use the structure object of the type weak_ENTRY_t as the value. The weak_entries member is the weak reference table entry.

    3. 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]; }; }}Copy the code

      Weak reference entities have two properties, one object, and the other property is a union that contains an array of weak references.

  • Objc_storeWeak analysis

    This method can be divided into three functions as a whole, as shown in the following figure:

    The data processing judgment of the current reference is to determine whether the reference has an old value and whether it points to a new value; If the reference is currently pointing to a value, that is, there is an old value, you need to clear the old value. At the same time, if the reference points to a new object, that is, there is a new value, the weak reference of the corresponding object needs to be initialized.

    Let’s start with a weak reference pointing to a new value and sort out the core code as follows:

    SideTables *newTable = &SideTables()[newObj]; SideTables *newTable = &SideTables()[newObj]; // Retrieve the weak reference table from the hash table, NewObj = (objc_object *)weak_register_no_lock(&newTable-> Weak_table, (id)newObj, location, crashIfDeallocating? CrashIfDeallocating : ReturnNilIfDeallocating);Copy the code

    Enter the weak_register_NO_lock process. See below:

    The first three arguments passed to the method are the weak reference table, the object, and the weak reference. This method determines if the object is destroyed, and if it is, it returns. If not, it will obtain the corresponding weak_entry_t of the object through the weak_entry_for_referent method, as shown in the following figure:

    Obtains the subscript in the table through the hash function, finds the corresponding subscript through the circular weak reference table, and obtains the corresponding weak_entrY_t. After getting weak_entry_t to store a new weak reference object, return to the method weak_register_no_lock, after calling weak_entry_for_referent, it will pass append_referrer(entry, referrer); Method for storing weak references. See below:

  • Conclusion:

    The hash table SideTable has a weak reference table (Weak_table_T). The weak reference table stores some entities (Weak_ENTRy_t). The entities include objects and weak reference arrays.

  • Old values exist for weak references

    The above analysis of the processing of a weak reference to a new value, what if there is a weak reference to an old value? This is the following code for the storeWeak method:

        if (haveOld) {
            weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
        }
    Copy the code

    Enter the weak_unregister_NO_lock method, see the following figure:

    In this process, the entity (Weak_entry) corresponding to the application object is obtained, and the remove_referrer method is called to remove the corresponding weak reference from the entity. See below:

    This procedure removes the reference from the object’s reference list and subtracts the number of weak references by one.

5. Weak reference clearing process

When an object calls the dealloc method, its weak references are handled in the following code:

- (void)dealloc { _objc_rootDealloc(self); } void _objc_rootDealloc(id obj) { ASSERT(obj); obj->rootDealloc(); } inline void objc_object::rootDealloc() { if (isTaggedPointer()) return; // fixme necessary? if (fastpath(isa.nonpointer && ! isa.weakly_referenced && ! isa.has_assoc && #if ISA_HAS_CXX_DTOR_BIT ! isa.has_cxx_dtor && #else ! isa.getClass(false)->hasCxxDtor() && #endif ! isa.has_sidetable_rc)) { assert(! sidetable_present()); free(this); } else { object_dispose((id)this); } } id object_dispose(id obj) { if (! obj) return nil; objc_destructInstance(obj); free(obj); return nil; } 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, /*deallocating*/true); obj->clearDeallocating(); } return obj; } inline void objc_object::clearDeallocating() { 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

Eventually, the clearDeallocating method is called to handle the weak reference. For objects with weak references, clearDeallocating_slow() is called; Method, the final weak reference clearance process is in the weak_clear_NO_lock. See below:

In this process, we first get the entity corresponding to the object in the weak reference table, turn on the loop, set all the weak references in the array to nil, and finally remove the entity from the weak reference table.

  • A data structure stored with weak references