The weak keyword is often used in daily development to solve the problem of circular references. The reason is that the reference count of the object referenced by weak does not increase, and the variable modified by weak is automatically empty when the object is released, which does not cause the problem of wild Pointers. Therefore, it is relatively safe. So how is weak implemented at the bottom? Next, let’s explore the implementation principle of weak.

Weak code location

We look at the underlying invocation of weak by viewing the assembly at the interrupt point.

weak
objc_initWeak

objc_initWeak
objc_initWeak
libobjc.A.dylib

Low-level code exploration (save logic)

objc_initWeak

We look for and locate the objc_initWeak function in the open source of libobjc.a.dylib.

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
  • Precondition judgment.
  • performstoreWeakThe storage operation.

storeWeak

It’s a long code, so let’s look at it separately.

assert(haveOld  ||  haveNew);
if(! haveNew) assert(newObj == nil); Class previouslyInitializedClass = nil; id oldObj; SideTable *oldTable; SideTable *newTable;Copy the code
  • Precondition judgment.
  • Declare both old and newSideTable.
if (haveOld) {
    oldObj = *location;
    oldTable = &SideTables()[oldObj];
} else {
    oldTable = nil;
}
if (haveNew) {
    newTable = &SideTables()[newObj];
} else {
    newTable = nil;
}
Copy the code
  • Global based on the new value and the old valueSideTablesTable, respectively assigned tooldTable.newTable.
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 +initializethen 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 setpreviouslyInitializedClass to recognize it on retry. previouslyInitializedClass = cls; goto retry; }}Copy the code
  • To prevent deadlocks in weak references and initializations, ensure that the object has been successfully initialized before weak references.
// Clean up old value, if any.
if (haveOld) {
    weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
Copy the code
  • Clear old values.
// Assign new value, if any.
if (haveNew) {
    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

    // 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.
    *location = (id)newObj;
}
else {
    // No new value. The storage is not changed.
}
Copy the code
  • Store new values (weak_register_no_lockFunction performs real storage logic.

weak_register_no_lock

Parameter interpretation

  • Weak_table Indicates the global weak reference table.
  • Referent Refers to a pointer to an object.
  • Referrer weak pointer address. Omit fault-tolerant logic and explore the main storage logic.
// 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{// create this array - insert weak_table weak_entry_t new_entry(referent, referrer); weak_grow_maybe(weak_table); weak_entry_insert(weak_table, &new_entry); }Copy the code
  • Declare aweak_entry_t *entry;Structure, which holds the pointer to the referenced object, andweakAddress of pointer.
  • According to the weak reference object pointer from globalweak_tableLook forentryIf it is foundentryThe insert operation is performed.
  • To modify values by directly manipulating the elements in the array,weak_entry_for_referentReturns a pointer to an element in the array.
  • If not, create a new oneweak_entry_tStruct array, just put thisweak_entry_tThe structure array is inserted intoweak_tableIn the.

summary

1. Find our weak_table through SideTable 2. Weak_table find or create weak_entry_t 3 according to referent. Append_referrer (entry, referrer) adds the object of my new weak reference to Entry 4. Finally, weak_entry_INSERT adds entry to our Weak_table

Low-level code exploration (null logic)

The object release is all in dealloc, so our null logic for weak reference objects should also be there.

- (void)dealloc {
    _objc_rootDealloc(self);
}
Copy the code
void
_objc_rootDealloc(id obj)
{
    assert(obj);

    obj->rootDealloc();
}
Copy the code
inline void
objc_object::rootDealloc()
{
    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
id 
object_dispose(id obj)
{
    if(! obj)return nil;

    objc_destructInstance(obj);    
    free(obj);

    return nil;
}
Copy the code
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
inline void 
objc_object::clearDeallocating()
{
    if(slowpath(! isa.nonpointer)) { // Slow pathfor raw pointer isa.
        sidetable_clearDeallocating();
    }
    else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
        // Slow path fornon-pointer isa with weak refs and/or side table data. clearDeallocating_slow(); } assert(! sidetable_present()); }Copy the code
NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
    assert(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));

    SideTable& table = SideTables()[this];
    table.lock();
    if (isa.weakly_referenced) {
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    if (isa.has_sidetable_rc) {
        table.refcnts.erase(this);
    }
    table.unlock();
}
Copy the code
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
  • dealloc > _objc_rootDealloc > rootDealloc > object_dispose > objc_destructInstance > clearDeallocating > clearDeallocating_slow > weak_clear_no_lock
  • inweak_clear_no_lockFrom the pointer to the referenced objectweak_tableFind the correspondingweak_entry_tStruct array.
  • The next loop empties the value of the weak pointer in the array and sets it all to nil.
  • Remove this struct array from the globalweak_tableIf the reference table is removed.