I will not say more about weak and will directly start our source code analysis tour

__weak

    id __week obj1 = obj;Copy the code

Compiler simulation code

    id obj1;
    obj1 = 0;
    objc_storeWeak(&obj1, obj);
    objc_storeWeak(&obj1, 0);Copy the code

The objc_storeWeak function takes the address of the assignment object of the second argument as the key value and registers the address of the __weak variable of the first argument into the weak table. If the second argument is 0, the address of the variable is removed from the weak table. The realization of the initWeak

id objc_initWeak(id *object, id value)
{
    *object = 0;
    return objc_storeWeak(object, value);
}Copy the code

StoreWeak is an open source part of Objective-C so let’s see how storeWeak is actually implemented, okay

objc_storeWeak

StoreWeak source official English notes quite full, you can directly understand ~

    // 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);
        // 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

Let’s look at the implementation of storeWeak to get oldObj/newObj

    if (HaveOld) {
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (HaveNew) {
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }Copy the code

The weak pointer points to the old object:

oldObj = *location;Copy the code

Then we get the SideTable object associated with the old and new objects:

oldTable = &SideTables()[oldObj];
newTable = &SideTables()[newObj];Copy the code

&SideTables()[oldObj] StripedMap implements a class that overloads the [] operator (c++: see me everywhere 233)

public:
    T& operator[] (const void *p) { 
        return array[indexForPointer(p)].value; 
    }Copy the code

The following is to remove the pointing information from the old object’s weak table and establish the association information in the new object’s weak table:

if (HaveOld) {
    weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
if (HaveNew) {
    newObj = weak_register_no_lock(&newTable->weak_table, newObj,location);
    // weak_register_no_lock returns NULL if weak store should be rejected
}Copy the code

Next let the weak reference pointer point to the new object:

*location = newObj;Copy the code

This new object is returned:

return newObj;Copy the code

Above, we can find that weak management is actually related to weak_table. Next, we will analyze weak_table!

weakTable

Close the source code or summarize… I’ve been thinking a lot… …)

  • Weak table is a weak reference table, implemented as a Weak_table_t structure, which stores all weak reference information related to all objects
  • 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.
  • Referrers in Weak_entry_t stores all variables pointing to the weak object

Here’s a picture to get a feel for it:

Let’s begin our analysis of these structures:

  • SideTable is a C++ class that is defined in nsobject.mm
    class SideTable {
    private:
      static uint8_t table_buf[SIDE_TABLE_STRIPE * SIDE_TABLE_SIZE];
    public:
      RefcountMap refcnts;// Reference count table
      weak_table_t weak_table;// Weak reference table. }Copy the code

The weak table is structurally defined as:

/** * The global weak references table. Stores object ids as keys, * and weak_entry_t structs as their values. */
struct weak_table_t {
    weak_entry_t *weak_entries;
    size_t    num_entries;
    uintptr_t mask;
    uintptr_t max_hash_displacement;
};Copy the code

Weak_entry_t = weak_entry_t; weak_entry_t = weak_entry_t; weak_entry_t = weak_entry_t; weak_entry_t = weak_entry_t; weak_entry_t = weak_entry_t;

Let’s look at the structure of Weak_entry_t

struct weak_entry_t {
    DisguisedPtr<objc_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_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };
};Copy the code

Referrers are all variables that refer to the weak object referent is an in-memory weak object

Now what conclusions can we draw

  1. Weak reference variables in OC are managed using the weak table (Hash table)
  2. Weak_entries in the weak table manage variables pointing to weak objects

The weak object was released

What does the program do when it frees an object, while discarding an object that no one is holding?

  • objc_release
  • Because the reference count is 0, dealloc is executed
  • objc rootDealloc
  • objc dispose
  • objc destructInstance
  • objc clear dealloctaing

The objc_clear_deallocating function, the last one called when the object was abandoned, does the following: (1) Retrieving the address of the abandoned object as a key from the weak table. (2) Delete from the weak table all addresses contained in the record with the __weak modifier variable assigned to nil (3) delete the record from the weak table. (4) Delete the address of the discarded object from the reference count table.

It can be seen from this that if used in large quantities, there will beWeak modifier, which consumes the corresponding CPU resources. A good strategy is to use it only when you need to avoid circular referencesWeak modifier.

Release objects immediately

{
    id __weak obj = [[NSObject alloc] init];
}Copy the code

Because the source code assigns a generated and held object to a variable with an __weak modifier, it cannot hold the object itself, which is released and deprecated.

Using a variable with an __weak modifier is using an object registered in Autoreleasepool

{
    id __weak obj1 = obj;
    NSLog(@"% @",obj1);
}Copy the code

Code that the compiler emulates

    id obj1;
    objc_initWeak(&obj,obj);
    id tmp = objc_loadWeakRetained(&obj);
    objc_autorelease(tmp);
    NSLog(@"% @", tmp);
    objc_destoryWeak(&obj1);Copy the code

Notice these two lines of code

    id tmp = objc_loadWeakRetained(&obj);
    objc_autorelease(tmp);Copy the code

Compared to assignment, we added calls to objc_loadWeakRetained and objc_autorelease with an __weak modifier attached. Retained (1) The objc_loadWeakRetained function retrieves the object referenced with the __weak modifier variable and retains (2) the objc_autorelease function registers the object with Autoreleasepool.

Note: Each time you use the weak modifier variable, the object referenced by the variable is registered with autoReleasepool. To avoid this, assign a variable with the weak modifier to a variable with the __strong modifier and use it again.

id __weak o = obj;
id tmp = o;Copy the code

allowWeakReference/retainWeakReference

When allowsWeakReference/retainWeakReference instance methods (with NO written NSObject interface documentation) to return to the NO.

- (BOOL)allowsWeakReference;
- (BOOL)retainWeakReference;Copy the code

If the allowsWeakReference method returns NO when assigning a variable to the __weak modifier, the program terminates abnormally. When the object is retained, if the retainWeakReference method returns NO, this variable will be used nil

Specific source code analysis

The above about weak

  • weak_register_no_lock
  • weak_unregister_no_lock
  • . Many implementations are missing… I posted the code I read with comments… Those interested can take a look at the implementation… Feel the charm of source code implementation

Open source part of ObjC

Objc_object **referrer

objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;Copy the code

This is understood in conjunction with the remove_referrer function

remove_referrer

for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            if (entry->inline_referrers[i] == old_referrer) {
                entry->inline_referrers[i] = nil;
                return; }}Copy the code

So we’re going to get the referrer and we’re going to compare that value to the pointer in the entry list, and if it’s found, it’s nil and you might have confused TaggedPoint in the source code

(The pointer made me dizzy… Admire C/C ++ system engineers) (Reading the source code is really an interesting thing lol)

#include "objc-private.h"

#include "objc-weak.h"

#include <stdint.h>
#include <stdbool.h>
#include <sys/types.h>
#include <libkern/OSAtomic.h>

#define TABLE_SIZE(entry) (entry->mask ? entry->mask + 1 : 0)

static void append_referrer(weak_entry_t *entry, objc_object **new_referrer);

BREAKPOINT_FUNCTION(
    void objc_weak_error(void));/** * Unique hash function for object pointers only. * * @param key The object pointer * * @return Size unrestricted hash of pointer. */
static inline uintptr_t hash_pointer(objc_object *key) {
    return ptr_hash((uintptr_t)key);
}

/** * Unique hash function for weak object pointers only. * * @param key The weak object pointer. * * @return Size unrestricted hash of pointer. */
static inline uintptr_t w_hash_pointer(objc_object **key) {
    return ptr_hash((uintptr_t)key);
}

/** * Grow the entry's hash table of referrers. Rehashes each * of the referrers. * * @param entry Weak pointer hash set  for a particular object. */
__attribute__((noinline, used))
static void grow_refs_and_insert(weak_entry_t *entry, 
                                 objc_object **new_referrer)
{
    assert(entry->out_of_line);

    size_t old_size = TABLE_SIZE(entry);
    size_t new_size = old_size ? old_size * 2 : 8;

    size_t num_refs = entry->num_refs;
    weak_referrer_t *old_refs = entry->referrers;
    entry->mask = new_size - 1;

    entry->referrers = (weak_referrer_t *)
        calloc(TABLE_SIZE(entry), sizeof(weak_referrer_t));
    entry->num_refs = 0;
    entry->max_hash_displacement = 0;

    for (size_t i = 0; i < old_size && num_refs > 0; i++) {
        if (old_refs[i] != nil) {
            append_referrer(entry, old_refs[i]);
            num_refs--;
        }
    }
    // Insert
    append_referrer(entry, new_referrer);
    if (old_refs) free(old_refs);
}

/** * Add the given referrer to set of weak pointers in this entry. * Does not perform duplicate checking (b/c weak pointers are never * added to a set twice). * * @param entry The entry holding the set of weak pointers. * @param new_referrer The new weak pointer to be added. */
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
    // if is Array implementation
    if (! entry->out_of_line) {
        // Try to insert inline.
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            if (entry->inline_referrers[i] == nil) {
                entry->inline_referrers[i] = new_referrer;
                return; }}// Couldn't insert inline. Allocate out of line.
        weak_referrer_t *new_referrers = (weak_referrer_t *)
            calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
        // This constructed table is invalid, but grow_refs_and_insert
        // will fix it and rehash it.
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            new_referrers[i] = entry->inline_referrers[i];
        }
        entry->referrers = new_referrers;
        entry->num_refs = WEAK_INLINE_COUNT;
        entry->out_of_line = 1;
        entry->mask = WEAK_INLINE_COUNT- 1;
        entry->max_hash_displacement = 0;
    }

    assert(entry->out_of_line);

    if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
        return grow_refs_and_insert(entry, new_referrer);
    }

    //find a place to insert ref
    //weak_entry_remove() may bzero() some place
    size_t index = w_hash_pointer(new_referrer) & (entry->mask);
    size_t hash_displacement = 0;
    while(entry->referrers[index] ! =NULL) {
        index = (index+1) & entry->mask;
        hash_displacement++;
    }
    if (hash_displacement > entry->max_hash_displacement) {
        entry->max_hash_displacement = hash_displacement;
    }
    weak_referrer_t &ref = entry->referrers[index];
    ref = new_referrer;
    entry->num_refs++;
}

/** * Remove old_referrer from set of referrers, if it's present. * Does not remove duplicates, because duplicates should not exist. * * @todo this is slow if old_referrer is not present. Is this ever the case? * * @param entry The entry holding the referrers. * @param old_referrer The referrer to remove. */
static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
    if (! entry->out_of_line) {
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            if (entry->inline_referrers[i] == old_referrer) {
                entry->inline_referrers[i] = nil;
                return;
            }
        }
        _objc_inform("Attempted to unregister unknown __weak variable "
                     "at %p. This is probably incorrect use of "
                     "objc_storeWeak() and objc_loadWeak(). "
                     "Break on objc_weak_error to debug.\n", 
                     old_referrer);
        objc_weak_error();
        return;
    }

    size_t index = w_hash_pointer(old_referrer) & (entry->mask);
    size_t hash_displacement = 0;
    while(entry->referrers[index] ! = old_referrer) { index = (index+1) & entry->mask;
        hash_displacement++;
        if (hash_displacement > entry->max_hash_displacement) {
            _objc_inform("Attempted to unregister unknown __weak variable "
                         "at %p. This is probably incorrect use of "
                         "objc_storeWeak() and objc_loadWeak(). "
                         "Break on objc_weak_error to debug.\n", 
                         old_referrer);
            objc_weak_error();
            return;
        }
    }
    entry->referrers[index] = nil;
    entry->num_refs--;
}

/** * Add new_entry to the object's table of weak references. * Does not check whether the referent is already in the table. */
static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)
{
    weak_entry_t*weak_entries = weak_table->weak_entries; assert(weak_entries ! = nil);//mask may keep entry in array
    size_t index = hash_pointer(new_entry->referent) & (weak_table->mask);
    size_t hash_displacement = 0;

    / / the hash index processing
    while(weak_entries[index].referent ! = nil) { index = (index+1) & weak_table->mask;
        hash_displacement++;
    }


    weak_entries[index] = *new_entry;
    weak_table->num_entries++;

    //update max_hash_displacement
    if(hash_displacement > weak_table->max_hash_displacement) { weak_table->max_hash_displacement = hash_displacement; }}static void weak_resize(weak_table_t *weak_table, size_t new_size)
{
    size_t old_size = TABLE_SIZE(weak_table);

    weak_entry_t *old_entries = weak_table->weak_entries;
    weak_entry_t *new_entries = (weak_entry_t *)
        calloc(new_size, sizeof(weak_entry_t));

    weak_table->mask = new_size - 1;
    //new
    weak_table->weak_entries = new_entries;
    weak_table->max_hash_displacement = 0;
    weak_table->num_entries = 0;  // restored by weak_entry_insert below

    //use pointer
    if (old_entries) {
        weak_entry_t *entry;
        weak_entry_t *end = old_entries + old_size;
        for (entry = old_entries; entry < end; entry++) {
            if(entry->referent) { weak_entry_insert(weak_table, entry); }}free(old_entries); }}// Grow the given zone's table of weak references if it is full.
static void weak_grow_maybe(weak_table_t *weak_table)
{
    size_t old_size = TABLE_SIZE(weak_table);

    // Grow if at least 3/4 full.
    if (weak_table->num_entries >= old_size * 3 / 4) {
        weak_resize(weak_table, old_size ? old_size*2 : 64); }}// Shrink the table if it is mostly empty.
static void weak_compact_maybe(weak_table_t *weak_table)
{
    size_t old_size = TABLE_SIZE(weak_table);

    // Shrink if larger than 1024 buckets and at most 1/16 full.
    if (old_size >= 1024  && old_size / 16 >= weak_table->num_entries) {
        weak_resize(weak_table, old_size / 8);
        // leaves new table no more than 1/2 full}}/** * Remove entry from the zone's table of weak references. */
static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
{
    // remove entry
    if (entry->out_of_line) free(entry->referrers);

    The bzero() function places n zeros in the region pointed to by s.
    bzero(entry, sizeof(*entry));

    weak_table->num_entries--;

    //maybe resize weak_table
    weak_compact_maybe(weak_table);
}


/** * 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 index = hash_pointer(referent) & weak_table->mask;
    size_t hash_displacement = 0;
    while(weak_table->weak_entries[index].referent ! = referent) { index = (index+1) & weak_table->mask;
        hash_displacement++;
        if (hash_displacement > weak_table->max_hash_displacement) {
            returnnil; }}return &weak_table->weak_entries[index];
}

/** * Unregister an already-registered weak reference. * This is used when referrer's storage is about to go away, but referent * isn't dead yet. (Otherwise, zeroing referrer later would be a * bad memory access.) * Does nothing if referent/referrer is not a currently active weak reference. * Does not zero referrer. * * FIXME currently requires old referent value to be passed in (lame) * FIXME  unregistration should be automatic if referrer is collected * * @param weak_table The global weak table. * @param referent The object. * @param referrer The weak reference. */
void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, 
                        id *referrer_id)
{
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;

    weak_entry_t *entry;

    if(! referent)return;


    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        remove_referrer(entry, referrer);
        bool empty = true;
        //after unregister the entry's referrers is empty?
        // Hash implementation
        if(entry->out_of_line && entry->num_refs ! =0) {
            empty = false;
        }
        // Array implementation
        else {
            for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
                if (entry->inline_referrers[i]) {
                    empty = false; 
                    break; }}}// if entry.references empty
        if(empty) { weak_entry_remove(weak_table, entry); }}// Do not set *referrer = nil. objc_storeWeak() requires that the 
    // value not change.
}

/** * Registers a new (object, weak pointer) pair. Creates a new weak * object entry if it does not exist. * * @param weak_table The global weak table.  * @param referent The object pointed to by the weak reference. * @param referrer The weak pointer address. */
id 
weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, bool crashIfDeallocating)
{
    //object
    objc_object *referent = (objc_object *)referent_id;

    //The Point which point the object
    objc_object **referrer = (objc_object **)referrer_id;

    if(! referent || referent->isTaggedPointer())return referent_id;

    // ensure that the referenced object is viable
    // judge is Allows Weak Reference
    bool deallocating;
    if(! referent->ISA()->hasCustomRR()) { deallocating = referent->rootIsDeallocating(); }else {
        BOOL (*allowsWeakReference)(objc_object *, SEL) = 
            (BOOL(*)(objc_object *, SEL))
            object_getMethodImplementation((id)referent, 
                                           SEL_allowsWeakReference);
        if ((IMP)allowsWeakReference == _objc_msgForward) {
            return nil;
        }
        deallocating =
            ! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
    }

    if (deallocating) {
        if (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 {
            returnnil; }}// 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;
        new_entry.referent = referent;
        new_entry.out_of_line = 0;
        new_entry.inline_referrers[0] = referrer;
        for (size_t i = 1; i < WEAK_INLINE_COUNT; i++) {
            new_entry.inline_referrers[i] = nil;
        }

        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;
}


#if DEBUG
bool
weak_is_registered_no_lock(weak_table_t *weak_table, id referent_id) 
{
    return weak_entry_for_referent(weak_table, (objc_object *)referent_id);
}
#endif


/** * 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) 
{

    //referent objc
    objc_object *referent = (objc_object *)referent_id;

    //referent objc entry(which save many referents)
    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;
    }

    //entry->referrers all nil
    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);
}


/** * This function gets called when the value of a weak pointer is being * used in an expression. Called by objc_loadWeakRetained() which is * ultimately called by objc_loadWeak(). The objective is to assert that * there is in fact a weak pointer(s) entry for this particular object being * stored in the weak-table, and to retain that object so it is not deallocated * during the weak pointer's usage. * * @param weak_table * @param referrer The weak pointer address. */
/* Once upon a time we eagerly cleared *referrer if we saw the referent was deallocating. This confuses code like NSPointerFunctions which tries to pre-flight the raw storage and assumes if the storage is zero then the weak system is done interfering. That is false: the weak system is still going to check and clear the storage later. This can cause objc_weak_error complaints and crashes. So we now don't touch the storage until deallocation completes. */
id 
weak_read_no_lock(weak_table_t *weak_table, id *referrer_id) 
{
    objc_object **referrer = (objc_object **)referrer_id;
    objc_object *referent = *referrer;

    //Detection Tagged Pointer
    if (referent->isTaggedPointer()) return (id)referent;

    weak_entry_t *entry;
    // referent == nil or entry == nil
    if(referent == nil || ! (entry = weak_entry_for_referent(weak_table, referent))) {return nil;
    }
    //Custom RR denotes a custom retain-release implementation
    //
    if (! referent->ISA()->hasCustomRR()) {
        / /??? question
        if (! referent->rootTryRetain()) {
            returnnil; }}//has isa
    else {
        BOOL (*tryRetain)(objc_object *, SEL) = (BOOL(*)(objc_object *, SEL))
            object_getMethodImplementation((id)referent, 
                                           SEL_retainWeakReference);
        //IMP ! = _objc_magForward
        if ((IMP)tryRetain == _objc_msgForward) {
            return nil;
        }
        //IMP ! = nil
        if (! (*tryRetain)(referent, SEL_retainWeakReference)) {
            returnnil; }}return (id)referent;
}Copy the code

“Gold digging Technology Essay” juejin.cn/post/684490…