weak

Let’s restore a small scene:

Interviewer: What is the role of weak in OC and its basic principle?

You: Weak is a modifier. In order to avoid memory leaks caused by strong references between objects, we use weak to modify objects. 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. The principle of weak is that the Runtime maintains a hashMap, and the system uses a series of hash algorithms to store weak objects in the form of key-values in this hashMap. …………………

Come to… Take a seat. Is that what everyone says? In fact, THAT’s what I said in the interview. Until one day I found a UML diagram like this:

Then I realized (B) that the whole data structure of the weak was meng, and then I studied the source code of object according to my own understanding and added the simple (FEI) simple (BA) simple (La).

See here whether you also don (meng) understand (B). Ha ha………………… Hold on, hold on. Let me look at the code. The following code is all in the source code environment, you can click here to source code


Create a weak object, set a breakpoint, and then set up assembly mode. Debug –> Debug Work flow –>Always Show Disassembly

And then you’ll find yourself in one of these amazing breakpoints, objc_initWeak

Then you do a global search and you’ll find the code below

objc_initWeak(id *location, id newObj){ if (! NewObj) {// If the current object is empty, return nil *location = nil; return nil; Return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating> (location, (objc_object*)newObj); }Copy the code
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) {
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (haveNew) {
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }
    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
    
    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()) 
        {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            class_initialize(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;

            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 ? CrashIfDeallocating : ReturnNilIfDeallocating);
        // weak_register_no_lock returns nil if weak store should be rejected

        // Set is-weakly-referenced bit in refcount table.
        if (!newObj->isTaggedPointerOrNil()) {
            newObj->setWeaklyReferenced_nolock();
        }
        // Do not set *location anywhere else. That would introduce a race.
        *location = (id)newObj;
    }
    else {
        // No new value. The storage is not changed.
    }

    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
    
    // This must be called without the locks held, as it can invoke
    // arbitrary code. In particular, even if _setWeaklyReferenced
    // is not implemented, resolveInstanceMethod: may be, and may
    // call back into the weak reference machinery.

    callSetWeaklyReferenced((id)newObj);

    return (id)newObj;
}

///存储
weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions)
{
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;

    if (referent->isTaggedPointerOrNil()) return referent_id;

    // ensure that the referenced object is viable
    if (deallocatingOptions == ReturnNilIfDeallocating ||
        deallocatingOptions == CrashIfDeallocating) {
        bool deallocating;
        if (!referent->ISA()->hasCustomRR()) {
            deallocating = referent->rootIsDeallocating();
        }
        else {
            // Use lookUpImpOrForward so we can avoid the assert in
            // class_getInstanceMethod, since we intentionally make this
            // callout with the lock held.
            auto allowsWeakReference = (BOOL(*)(objc_object *, SEL))
            lookUpImpOrForwardTryCache((id)referent, @selector(allowsWeakReference),
                                       referent->getIsa());
            if ((IMP)allowsWeakReference == _objc_msgForward) {
                return nil;
            }
            deallocating =
            ! (*allowsWeakReference)(referent, @selector(allowsWeakReference));
        }

        if (deallocating) {
            if (deallocatingOptions == 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;
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        append_referrer(entry, referrer);
    } 
    else {
        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;
}

///
/** 
 * Return the weak reference table entry for the given referent. 
 * If there is no entry for referent, return NULL. 
 * Performs a lookup.
 *
 * @param weak_table 
 * @param referent The object. Must not be nil.
 * 
 * @return The table of weak referrers to this object. 
 */


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;
    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);
        hash_displacement++;
        if (hash_displacement > weak_table->max_hash_displacement) {
            return nil;
        }
    }
    
    return &weak_table->weak_entries[index];
}

Copy the code

Ha ha ha is not scold me, the whole TM is the source code, yes I feel the source code is the best thing, the above key notes I have done the translation, slowly see you can understand, brother dei ~ refuelling 💪🏻


Next, take a look at the dealloc source code

Do a global search in the Object source environment

// replace by CF (throws an NS Exception) // The class method is not implemented. An Exception is returned. + (void)dealloc {} // Replace by NSZombies - (void)dealloc { _objc_rootDealloc(self); }Copy the code

_objc_rootDealloc —> 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)) {self assert(! sidetable_present()); free(this); Object_dispose ((id)this);} else {// Please dispose(id)this); }}Copy the code

You can follow my steps step by step, or you can do your own research

object_dispose —>object_dispose—>objc_destructInstance—>clearDeallocating — clearDeallocating_slow —>weak_clear_no_lock

/** * Called by dealloc; Nils out all weak Pointers that point to the * called by dealloc; 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

conclusion

  • Initialization:runtimeWill be calledobjc_initWeakFunction to initialize a new oneweakPointer to the address of an object.
  • When adding a reference:objc_initWeakThe function will callobjc_storeWeak()The function,objc_storeWeak()Update pointer pointer to create the corresponding weak reference table.
  • Release when calledclearDeallocatingFunction.clearDeallocatingThe function first retrieves all objects based on their addressweakArray of pointer addresses, and then iterate through that array and set the data in that array tonilAnd finally put thisentryfromweakDelete from table, and finally clean up the object’s record.