preface

The previous article covered reference counting tables in hash tables, but left out one weak reference table. What does the reference count table do when weak is used for weak references? This article will analyze the weak reference table from __weak.

Weak principle

  • Let’s start with the following code:

    NSObject *obj = [NSObject alloc];
    NSLog(@"%ld -- %@".CFGetRetainCount((__bridge CFTypeRef)(obj)), obj);
    __weak typeof(id) wushuang = obj;
    
    NSLog(@"%ld -- %@".CFGetRetainCount((__bridge CFTypeRef)(obj)), obj);
    NSLog(@"%ld -- %@".CFGetRetainCount((__bridge CFTypeRef)(wushuang)), wushuang);
    NSLog(@"%ld -- %@".CFGetRetainCount((__bridge CFTypeRef)(wushuang)), wushuang);
    NSLog(@"%ld -- %@".CFGetRetainCount((__bridge CFTypeRef)(obj)), obj);
    Copy the code

    The print result is as follows:



    You might wonder, what does a weak reference actually do? Why is the reference count of the weak pointer 2? Wushuang and obj are both Pointers. Why is the reference count of obj unchanged? With that in mind, let’s explore the objC4-818.2 source code

objc_initWeak

  • Looking at the assembly, you can see that adding the __weak modifier leads to the objc_initWeak function, a process that is determined by LLVM.

    id
    objc_initWeak(id *location, id newObj) // Location weak pointer address, newObj object
    {
        if(! newObj) { *location = nil;return nil;
        }
        return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
          (location, (objc_object*)newObj);
    }
    Copy the code

    In this code, location is the weak pointer, newObj is the NSObject object, and will eventually go to storeWeak. Observe that objc_destroyWeak will also go to storeWeak, but with different parameters

  • The main code of storeWeak is as follows

    enum CrashIfDeallocating {
      DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
    };
    template <HaveOld haveOld, HaveNew haveNew,
            enum CrashIfDeallocating crashIfDeallocating>
    static id 
    storeWeak(id *location, objc_object *newObj)
    {
        ASSERT(haveOld  ||  haveNew);
        if(! haveNew)ASSERT(newObj == nil);
        Class previouslyInitializedClass = nil;
        id oldObj;
        SideTable *oldTable;
        SideTable *newTable;
     retry:
        if (haveOld) { ... } else{... }if (haveNew) {
            newTable = &SideTables()[newObj]; // Get the hash table of the object
        } else{... } SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);// Lock the hash table
    
        if(haveOld && *location ! = oldObj) { ... }// Deadlocks between the weak reference mechanism and the + initialization mechanism can be prevented by ensuring that no weak reference object has an uninitialized ISA
        if (haveNew  &&  newObj) { ... }
        // Call the method when it is destroyed
        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()) {... }// Do not set *location anywhere else. That would introduce a race.
          *location = (id)newObj;
      }
      else { }
      
      SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); / / unlock
      callSetWeaklyReferenced((id)newObj);
      return (id)newObj;
    }
    Copy the code

    According to the code analysis, the weak_register_NO_lock function is eventually entered

  • Weak_register_no_lock:

    id 
    weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                        id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions)
    {
        objc_object *referent = (objc_object *)referent_id; // Referent: newObject, NSObject object
        objc_object **referrer = (objc_object **)referrer_id; // Location pointer, weak pointer
        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(a);/ / returns false
            }
            else{... }if(deallocating) { ... }}// now remember it and where it is being stored
        weak_entry_t *entry;
        if ((entry = weak_entry_for_referent(weak_table, referent))) { // Get the host object's weak_entry_t based on the object
            append_referrer(entry, referrer); // Insert if present
        } 
        else { // Create weak_entry_t if it does not exist
            weak_entry_t new_entry(referent, referrer); / / create weak_entry_t
            weak_grow_maybe(weak_table); // Check the size of weak_table to determine whether expansion is required
            weak_entry_insert(weak_table, &new_entry); / / insert
        }
        // Do not set *referrer. objc_storeWeak() requires that the 
        // value not change.
        return referent_id;
    }
    Copy the code
    • The core of the method is the basisreferentObject to findweak_tableIn theweak_entriesA linked listweak_entry_tobjectentry:
      • ifentryExists, willWeak pointerdepositweak_entry_tIn thereferrersIn a linked list, store whenFollow the 3/4 double expansion rule.
      • ifentryDoes not exist, according toobjectandWeak pointerCreate a new oneentryAnd thenentryinsertweak_tabletheweak_entriesChain in the table.

According to the analysis, __weak does not cause the change of the reference count, so that means the reason for the change of the reference count of the weak object is related to the call of the weak object when printing. Through the breakpoint assembly analysis, the code will go to the objc_loadWeak method when printing

objc_loadWeak

  • The code is as follows:

    id
    objc_loadWeak(id *location)
    {
        if(! *location)return nil;
        return objc_autorelease(objc_loadWeakRetained(location));
    }
    Copy the code

    The code is mainly the result returned by objc_loadWeakRetained call, and then call the objc_autorelease method. The core code is positioned to objc_loadWeakRetained

  • Objc_loadWeakRetained:

    id
    objc_loadWeakRetained(id *location)
    {
        id obj;
        id result;
        Class cls;
        SideTable *table;
     retry:
        // fixme std::atomic this load
        obj = *location; // Get the object object according to the weak pointer
        if (obj->isTaggedPointerOrNil()) return obj;
      
        table = &SideTables()[obj]; // Get the hash table based on the object
        table->lock(a);/ / lock
        if(*location ! = obj) { ... } result = obj; cls = obj->ISA(a);if (! cls->hasCustomRR()) {
            ASSERT(cls->isInitialized());
            if (! obj->rootTryRetain()) { // obj reference count changesresult = nil; }}else{... } table->unlock(a);return result;
    }
    Copy the code
    • Based on the breakpoint walk, the final code is inrootTryRetain
  • RootTryRetain:

    ALWAYS_INLINE bool 
    objc_object::rootTryRetain(a)
    {
        return rootRetain(true, RRVariant::Fast) ? true : false;
    }
    Copy the code

    We ended up with the familiar rootRetain code, where the reference count was +1.

  • When the weak object is used, the reference count is +1 for a short time. However, after the weak object is used, the objc_release method is called to perform the reference count -1. Therefore, when WusHuang is printed for many times, the reference count is always 2

objc_destroyWeak

  • When weak objects are destroyed, objc_destroyWeak will be called, and finally the code will enter storeWeak. According to the above analysis, the core code of destruction is to call Weak_unregister_NO_lock, and the core code is as follows:

    void
    weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, 
                          id *referrer_id)
    {
        objc_object *referent = (objc_object *)referent_id; / / object
        objc_object **referrer = (objc_object **)referrer_id; / / weak pointer
        weak_entry_t *entry;
    
        if(! referent)return;
        if ((entry = weak_entry_for_referent(weak_table, referent))) { 
            remove_referrer(entry, referrer); // remove referrer // remove weak reference pointer
            bool empty = true;
            if (entry->out_of_line() && entry->num_refs ! =0) {
                empty = false;
            }
            else {
                for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
                    if (entry->inline_referrers[i]) {
                        empty = false; 
                        break; }}}if (empty) {
                weak_entry_remove(weak_table, entry); / / remove the entry}}}Copy the code

    This part of the code is easy to understand, which is to obtain the entry based on the object and weak reference table, then delete the weak pointer, and finally remove the entry

Weak reference table structure diagram

  • The structure diagram of the weak reference table is as follows: