👨 🏻 💻 making Demo

Easy to remember:

  • Function: As a weak reference modifier, it does not increase the reference count of an object, nor does it hold an object. When the object disappears, the pointer automatically becomes nil
  • Weak is a hash table where Key is the address of the object and Value is the address array of the weak pointer
  • Underlying implementation process
    • Initialization: The Runtime calls the objc_initWeak function and initializes a new weak pointer to the address of the object
    • When adding a reference: the objc_initWeak function calls the objc_storeWeak() function, which updates the pointer pointer and creates the corresponding weak reference table
    • When locating: Call clearDeallocating, the object address gets an array of weak pointer addresses, and then iterates through that array to set everything in it to nil

citation

Before, we only recognized weak as a weak reference attribute modifier, which does not increase the reference count of the object or hold the object. After the object disappears, the pointer automatically becomes nil. In ARC environments, to avoid circular references, the delegate property is often decorated with weak.

Weak is a hash table. Key is the address of the object and Value is the address array of the weak pointer.

By the way, it’s actually quite difficult to loop references, Xcode will have a warning, yellow is quite conspicuous…

Realize the principle of

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

The underlying implementation is roughly divided into three steps:

  • 1. Initialization: The Runtime calls the objc_initWeak function and initializes a new weak pointer to the address of the object. (Give you a dry brother)
  • 2. When adding a reference: the objc_initWeak function will call objc_storeWeak(), which updates the pointer pointer and creates the corresponding weak reference table. (Give this dry brother a hukou and introduce him to relatives and friends)
  • 3. When releasing, call the clearDeallocating function. The clearDeallocating function first fetches an array of weak pointer addresses based on the object’s address, then iterates through the array to set it to nil, deletes the entry from the Weak table, and clears the object’s record. (Finally, when the dry brother finished his work, he was put to death.)

Parsing the internal implementation step by step:

1) Initialization: The Runtime calls the objc_initWeak function, which initializes a new weak pointer to the address of the object.

NSObject *obj = [[NSObject alloc] init];
id __weak obj1 = obj; // Watch your weak
Copy the code

When we initialize a weak variable, the Runtime calls the objc_initWeak function in nsobject. mm. This function is declared in Clang as follows:

id objc_initWeak(id *object, id value);
Copy the code

An implementation of the objc_initWeak() method

id objc_initWeak(id *location, id newObj) {
    // Check whether the object instance is valid
    // An invalid object directly causes the pointer to be released
    if(! newObj) { *location = nil;return nil;
    }
    // Three bool values are passed
    // Use template for constant parameter passing to optimize performance
    return storeWeakfalse/*old*/.true/*new*/.true/*crash*/>
    (location, (objc_object*)newObj);
}
Copy the code

As you can see, this function is just a call entry to a deeper function. In general, entry functions make some simple judgments (such as the cache judgment in objc_msgSend), which determines whether the class object to which the pointer points is valid. Otherwise, object is registered as an __weak object pointing to value.

Note: The objc_initWeak function has one prerequisite: object must be a valid pointer that is not registered as an __weak object. Value can either be null or point to a valid object.

2) When adding a reference: objc_initWeak calls objc_storeWeak(), which updates the pointer pointer and creates the corresponding weak reference table.

The objc_storeWeak function is declared as follows:

id objc_storeWeak(id *location, id value);
Copy the code

An implementation of the objc_storeWeak method:

// HaveOld: true - Variable has a value
// false - Needs to be cleaned up in time, the current value may be nil
// HaveNew: true - The new value that needs to be assigned. The current value may be nil
// false - No new value needs to be assigned
// CrashIfDeallocating: true - newObj has been freed or newObj does not support weak references, and the process needs to be paused
// false - Replace storage with nil
template bool HaveOld, bool HaveNew, bool CrashIfDeallocating>
static id storeWeak(id *location, objc_object *newObj) {
    // This procedure is used to update the pointing of weak reference Pointers
    / / initialize previouslyInitializedClass pointer
    Class previouslyInitializedClass = nil;
    id oldObj;
    // Declare two sidetables
    // create an old hash
    SideTable *oldTable;
    SideTable *newTable;
    // Get the new value and the old value latch location (address as a unique identifier)
    // Create index flags by address to prevent bucket duplication
    // The operation pointed to below changes the old value
    retry:
    if (HaveOld) {
        // Change the pointer to get the address of the value stored with the oldObj index
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (HaveNew) {
        // Change the new value pointer to get the address of the value stored with newObj as index
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }
    // Lock operation to prevent multi-thread contention
    SideTable::lockTwoHaveOld, HaveNew>(oldTable, newTable);
    // Avoid thread collision reprocessing
    // 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::unlockTwoHaveOld, HaveNew>(oldTable, newTable);goto retry;
    }
    // Prevent weak reference deadlocks
    // And the +initialize constructor ensures non-null references to isa for all weak references
    if (HaveNew  &&  newObj) {
        // Get the ISA pointer to the new object
        Class cls = newObj->getIsa();
        Isa is not empty and has been initialized
        if(cls ! = previouslyInitializedClass && ! ((objc_class *)cls)->isInitialized()) {/ / unlock
            SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable);
            // Initialize its ISA pointer
            _class_initialize(_class_getNonMetaClass(cls, (id)newObj));
            The +initialize method is ideal if the class is already complete
            // If the class +initialize is in a thread
            // For example +initialize is calling the storeWeak method
            / / need to manually increase the protection strategy, and set the marked previouslyInitializedClass pointer
            previouslyInitializedClass = cls;
            // Try again
            gotoretry; }}// clear the old value
    if (HaveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }
    // assign a new value
    if (HaveNew) {
        newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table,
        (id)newObj, location,
        CrashIfDeallocating);
        The Weak_register_no_lock method returns nil if the weak reference is freed
        // Set the if reference flag bit in the reference count table
        if(newObj && ! newObj->isTaggedPointer()) {// Weak reference bit initialization
            The hash table's weak reference object is identified as weak in the reference count
            newObj->setWeaklyReferenced_nolock();
        }
        // Do not set the location object before; you need to change the pointer pointer here
        *location = (id)newObj;
    }
    else {
    // If there is no new value, no change is required
    }
    SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable);
    return (id)newObj;
}
Copy the code

Parse the above code

1) SideTable

Mainly used to manage the reference count and weak table of the object. Declare its data structure in nsobject.mm:

struct SideTable {
    // Ensure the atomic operation of the spin lock
    spinlock_t slock;
    // Reference count hash table
    RefcountMap refcnts;
    // weak references the global hash table
    weak_table_t weak_table;
}
Copy the code

Needless to say for the members of slock and refcnts, the first isa spinlock to prevent competing selection, and the second isa variable that helps the extra_rc co-reference count of the object’s isa pointer (for object results, mentioned in a future article). This section shows the structure and function of the weak global hash table.

(2) the weak form

Weak table is a weak reference table, implemented as a Weak_table_t structure, which stores all weak reference information related to an object.

In objc-weak.h it is defined as follows

struct weak_table_t {
    // Store all weak Pointers to the specified object
    weak_entry_t *weak_entries;
    // Storage space
    size_t    num_entries;
    // participate in judging the reference count auxiliary
    uintptr_t mask;
    // Maximum hash key offset
    uintptr_t max_hash_displacement;
};
Copy the code

This is a globally weak reference hash table. Use the address of an indefinite type object as the key and a Weak_entry_t type structure object as the value. Weak_entries members are, literally, weak reference table entries. The implementation is the same.

Weak_entry_t is an internal structure stored in the weak-reference table, which is responsible for maintaining and storing all weak-reference hash tables pointing to an object. Its definition is as follows:

typedef objc_object ** weak_referrer_t;
struct weak_entry_t {
    DisguisedPtrobjc_object> referent;
    union {
        struct {
            weak_referrer_t *referrers;
            uintptr_t        out_of_line : 1;
            uintptr_t        num_refs : PTR_MINUS_1;
            uintptr_t        mask;
            uintptr_t        max_hash_displacement;
        };
        struct {
            // out_of_line=0 is LSB of one of these (don't care which)
            weak_referrer_tinline_referrers[WEAK_INLINE_COUNT]; }; }}Copy the code

In the Weak_entry_t structure, the DisguisedPtr Referent is a encapsulation of the generic object pointer to solve the memory leak problem through the generic class. Write the out_OF_line member from the comment as the least significant bit. When it is 0, the Weak_REFERrer_t member will expand the multi-line static hash table.

In fact, weak_referrer_t is the alias of two-dimensional objC_object. Through a two-dimensional pointer address offset, subscript is used as the hash key to make a weak reference hash.

The value of out_of_line is usually equal to zero, so the weak reference table is always a two-dimensional array of objC_objective Pointers. A one-dimensional objc_objective pointer can form a weak-reference hash table. Multiple hash tables are realized through the third latitude, and the number of tables is WEAK_INLINE_COUNT.

StripedMap is a template class that has an array member that stores the PaddedT object, and the overloaded definition of [] returns the PaddedT value member, which is the T generic member we passed in. SideTable objects.

In the array subscript, indexForPointer is used to calculate the subscript by bit operation to achieve a static Hash Table. In weak_table, its member Weak_entry will encapsulate the address of the incoming object, and there is also an entry to access the global weak reference table.

Old object unregister operation Weak_unregister_no_lock

The main function of this method is to make the old object contact the corresponding binding of weak pointer in Weak_table. According to the function name, this is called the deregister operation. From the source, it can be known that its function is to contact weak pointer binding from weak_table. The traversal query is aimed at multiple weak reference hash tables in Weak_entry.

New object added register operation Weak_register_NO_lock

This step is contrary to the previous step. Weak_register_no_lock function is used to register the object of the heart and complete the binding operation with the corresponding weak reference table.

③ Initialize the flow list of weak reference objects

As can be seen from the above analysis, the main operations of initialization of weak reference table are fetching key, querying hash, creating weak reference table and other operations, which can be summarized as the following flow chart:

This diagram omits many cases of judgment, but the methods shown above are called when declaring an weak. Of course, the storeWeak method is not only used to declare weak, but also to operate on weak objects in class operations.

3) When releasing, call the clearDeallocating function. The clearDeallocating function first fetches an array of weak pointer addresses based on the object’s address, then iterates through the array to set it to nil, deletes the entry from the Weak table, and clears the object’s record.

What happens to the weak pointer when the object to which the weak reference points is released? When an object is released, the basic flow is as follows:

  • 1. Call objc_Release
  • 2. Execute dealloc because the reference count of the object is 0
  • 3. In dealloc, _objc_rootDealloc is called
  • 4. Call object_dispose in _objc_rootDealloc
  • 5. Call objc_destructInstance
  • Finally, call objc_clear_dealLocating

Focus on the objc_clear_deallocating function that is called when the object is released.

void objc_clear_deallocating(id obj) { assert(obj); assert(! UseGC);if (obj->isTaggedPointer()) return;
    obj->clearDeallocating();
}
Copy the code

That is, clearDeallocating was called. Tracing back, it ended up using an iterator to get the weak table value, called Weak_clear_NO_lock, looked up the corresponding value, and empty the weak pointer. Weak_clear_no_lock function is implemented as follows:

/** * Called by dealloc; nils out all weak pointers that point to the * provided object so that they can no longer be used. * * @param weak_table  * @param referent The object being deallocated. */
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);
    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;
    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;
            }
            else if (*referrer) {
                _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);
}
Copy the code

Objc_clear_deallocating is implemented as follows

  • 1. Obtain the address of the abandoned object from the weak table
  • 2. Assign nil to all addresses with weak modifiers included in the record
  • 3. Delete the weak table from the weak table
  • Delete the address of the discarded object from the reference count table

Note: The biggest advantage of hash table is to save and search the data consumption time greatly reduced, almost can be regarded as constant time; The trade-off is simply that it consumes more memory. But with more and more memory available today, it makes sense to trade space for time. In addition, easy coding is also one of its characteristics.

The above principles parse article source: http://www.cocoachina.com/ios/20170328/18962.html