The weak keyword is used for weak references. The counter of the referenced object is not incremented by one. The referenced object is set to nil when it is released.

Weak implementation structure

Weak is implemented through SideTables

SideTables has only 64 nodes in total, not 64 objects in APP

Therefore, multiple objects must reuse the same SideTable node

In other words, in a SideTable, weak_table is used as a hash table to store weak reference information of each object

The structure is as follows:

  • The hash table contains a weak reference tableweak_table
  • The weak reference table stores each weak reference instanceweak_entry
  • Weak-reference instances store each weak-reference objectweak_referrer

SideTable

The SideTable contains a spin lock, a reference count table, and a weak reference table

Struct SideTable {// Spinlock_t slock; // Hash table RefcountMap refcnts; Weak_table_t weak_table; // Weak references the global hash table weak_table_t weak_table; ... ... };Copy the code
  • Pinlock_t slock: Uint32_t type of unfair spin lock. Unfair means that the order in which the lock is acquired is independent of the order in which the lock is applied. The first thread that applies for the lock may acquire the lock last, or the thread that just acquired the lock will acquire the lock again immediately, resulting in a hungry wait. In the OC, _OS_UNfair_LOCK_OPAQUE also records information about the thread that acquired the lock. Only the thread that acquired the lock can unlock the lock.

  • RefcountMap Refcnts: Used to store the reference count of an object. It is essentially a hash table with objc_Object as its key, whose vaule is the reference count of the object. In addition, when the reference count of an object changes to 0, related information is automatically removed from the hash table.

  • Weak_table_t Weak_TABLE: Used to store information about weak references of OC objects. For details, see the following

weak_table_t

Weak_table is a typical hash structure. Weak_entry_t * Weak_entries is a dynamic array, which stores weak_entry_t weak reference information.

Weak_entry_t *weak_entries; struct weak_table_t {// weak_entry_t *weak_entries; Size_t num_entries; // The hash array length is -1, which will participate in the hash calculation. uintptr_t mask; // Uintptr_t max_hash_displacement; // Uintptr_t max_hash_displacement; };Copy the code

weak_entry_t

Each Weak_entry_t instance corresponds to weak reference information of an OC object, and its stored elements are each Weak_referrer_T (essentially Pointers to weak references to the object), that is, objC_object **new_referrer, You can manipulate the pointer to weak to point to nil after the object is destructed.

As shown in the annotations

#define WEAK_INLINE_COUNT 4 #define REFERRERS_OUT_OF_LINE 2 #if __LP64__ #define PTR_MINUS_2 62 #else #define PTR_MINUS_2 30 #endif struct weak_entry_t {// Weak_reference object pointer address DisguisedPtr<objc_object> Referent; // List of objects that reference the object, union. WEAK_INLINE_COUNT weak_Referrer_t inline_referrers[WEAK_INLINE_COUNT] WEAK_INLINE_COUNT [WEAK_INLINE_COUNT] weak_Inline_referrers [WEAK_INLINE_COUNT] WEAK_INLINE_COUNT (weak_referrer_t * Referrers) WEAK_INLINE_COUNT (WEAK_INLINE_COUNT); weak_referrer_t * Referrers (weak_referrer_t * Referrers); WEAK_INLINE_COUNT (WEAK_INLINE_COUNT); Considering that the number of weak reference Pointers generally does not exceed 'WEAK_INLINE_COUNT'. With fixed-length arrays, you don't need to allocate memory dynamically, but instead allocate a contiguous chunk of memory at a time. This will result in an increase in operational efficiency. Union {struct {weak_referrer_t *referrers; // Weak_referrer_t *referrers; Uintptr_t out_of_line_ness: 2; // Uintptr_t num_refs: PTR_MINUS_2; // The hash array length is -1, which will participate in the hash calculation. // ps: this is the length of the hash array, not the number of elements. The array might be 64 in length and only have 2 elements left. uintptr_t mask; // Uintptr_t max_hash_displacement; // Uintptr_t max_hash_displacement; }; struct { // out_of_line_ness field is low bits of inline_referrers[1] weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]; }; }; // REFERRERS_OUT_OF_LINE REFERRERS_OUT_OF_LINE Inline_referrers [1] stores pointer-aligned DisguisedPtr, whose lowest level must be 0b00 or 0b11, so 0b10 can be used to indicate the use of the dynamic array. // Returns true, Return (out_of_line_ness == REFERRERS_OUT_OF_LINE); return (out_of_line_ness == REFERRERS_OUT_OF_LINE); } // Assignment method weak_entry_t& operator=(const weak_entry_t& other) {memcpy(this, &other, sizeof(other)); return *this; Weak_entry_t (objc_object *newReferent, objC_object **newReferrer); weak_entry_t(objc_object ** newReferent); referent(newReferent) { inline_referrers[0] = newReferrer; for (int i = 1; i < WEAK_INLINE_COUNT; i++) { inline_referrers[i] = nil; }}};Copy the code

Weak initialization

Create one with the following codeNSObjectobjectobj1And generate another oneweakIdentification of theNSObjectobjectobj2Point to theobj1

objc_initWeak

Breakpoint into assembly to see the callobjc_initWeakmethods

objc_initWeakWhat’s called inside isstoreWeak

storeWeak

StoreWeak determines whether the class is initialized, and if not, initializes it and retry it. Then determine if any old values need to be deleted, and finally add new values.

StoreWeak is too long to map, attached with storeWeak code and annotations

// HaveOld: true - variable has value // false - need to be cleaned up in time, current value may be nil // HaveNew: True - New value to be assigned, the current value might be nil // false - no new value to be assigned // CrashIfDeallocating: True - newObj has been freed or newObj does not support weak references. // False - Store template <HaveOld HaveOld, HaveNew HaveNew, enum CrashIfDeallocating crashIfDeallocating> static id storeWeak(id *location, Objc_object * newObj) {/ / the procedure used to update a weak reference pointer pointing to ASSERT (haveOld | | haveNew); if (! haveNew) ASSERT(newObj == nil); / / initialize previouslyInitializedClass pointer Class previouslyInitializedClass = nil; id oldObj; SideTable *oldTable; SideTable *oldTable; SideTable *newTable; // Use the address to index the bucket to prevent it from repeating. // Use the following operations to change the old value retry: If (haveOld) {// change pointer to 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!) {// If (haveOld && *location! = oldObj) { SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); goto retry; } // prevent deadlock between weak references // and ensure that all weak references to isa are not empty if (haveNew && newObj) {// get the isa pointer to the new object Class CLS = newObj->getIsa(); If (CLS! = previouslyInitializedClass && ! (((objc_class *) CLS)->isInitialized()) {SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); Class_initialize (CLS, (id)newObj); class_initialize(CLS, (id)newObj); // If +initialize is in a thread // for example, +initialize is calling storeWeak and needs to manually add a protection policy to it. Mark previouslyInitializedClass = CLS and set previouslyInitializedClass pointer; goto retry; If (haveOld) {weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); If (haveNew) {newObj = (objc_object *) weak_register_no_lock(&newTable->weak_table, (id)newObj, location, crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating); Weak_register_no_lock method returns nil if the weak reference is released // Set the if reference flag bit in the reference count table if (! NewObj ->isTaggedPointerOrNil()) {// Weak reference bit initialoperation // reference count The weak reference object in the hash table is identified as weak reference in the reference count newObj->setWeaklyReferenced_nolock(); *location = (id)newObj; *location = (id)newObj; SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); // it must be called when there is no lock, because it can call any code: Even if _setWeaklyReferenced is not implemented, resolveInstanceMethod: may be called and called back to weaklyreferenced ((ID)newObj); return (id)newObj; }Copy the code

weak_register_no_lock

StoreWeak requires the operation weak_register_no_lock to assign new values.

The method first records the referenced object and its pointer, and after ensuring that the weakly referenced object has value and is not in the process of destruction, tries to obtain the old instance Weak_entry_t stored in weak_table.

  • Pass if there are old instancesappend_referrerMethod adds a reference object to the old instance
  • If it doesn’t exist, create a new oneweak_entry_tAnd through theweak_entry_insertinsertweak_tableIn the

Weak_register_no_lock source code analysis

id weak_register_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions) {/ / referent_id is new is a weak reference object objc_object * referent = (objc_object *)referent_id; // Referrer_id is the address of the __weak pointer objc_object **referrer = (objc_object **)referrer_id; If (referent->isTaggedPointerOrNil()) return referent_id; // If (referent->isTaggedPointerOrNil()) return referent_id; // Make sure the referenced object is available (no destructor, At the same time, should support the weak reference) 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)); } // The object being destructed, Locating 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; // Find the corresponding weak_entry in the weak_table, and add the referrer to the weak_entry if ((entry = weak_entry_for_referent(weak_table, referent))) { append_referrer(entry, referrer); Weak_entry_t new_entry(referent, referrer); 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; }Copy the code

weak_entry_for_referent

Then how to position weak_entry_t from weak_table?

Weak_entry_for_referent This function is called in weak_register_no_lock, which is weak_entry_for_referent.

The code and comments are attached below

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; // Make sure the index does not cross the boundary. This is the same as the mod operation, using bits instead to improve efficiency. 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! Weak_table ->mask; // Index +1, index = (index+1) & weak_table->mask; Weak_table (weak_table->weak_entries); // Index == begin if (index == begin) bad_weak_table(weak_table->weak_entries); hash_displacement++; Return nil if (hash_displacement > weak_table->max_hash_displacement) {return nil; } } return &weak_table->weak_entries[index]; }Copy the code

Next, how does weak_Entry insert a reference object

Add a weak reference object

After weak_entry instance is created, the weak-reference table will determine whether the weak_entry instance exists.

  • If it exists, it will goappend_referrer
  • If no, go aheadweak_entry_insert

Append_referrer (); append_referrer ()

append_referrer

static void append_referrer(weak_entry_t *entry, objc_object **new_referrer) { if (! Entry ->out_of_line()) {// If weak_entry does not already use a dynamic array for (size_t I = 0; i < WEAK_INLINE_COUNT; i++) { if (entry->inline_referrers[i] == nil) { entry->inline_referrers[i] = new_referrer; return; } // If the inline_referrers location is already full, then the inline_referrers will be converted to the dynamic array. 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_ness = REFERRERS_OUT_OF_LINE; entry->mask = WEAK_INLINE_COUNT-1; entry->max_hash_displacement = 0; } // Additional handling for dynamic arrays: ASSERT(entry->out_of_line()); // If the number of elements in the dynamic array is greater than or equal to 3/4 of the total array space, If (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {return grow_refs_and_insert(entry, new_referrer); Size_t begin = w_hash_pointer(new_referrer) & (entry->mask); // Make sure that the position of BEGIN can only be greater than or equal to the length of the array. // Hash index size_t index = begin; Size_t hash_displacement = 0; size_t hash_displacement = 0; // Where weak_entry_t hash algorithm is the same as weak_table_t hash algorithm, while (entry->referrers[index]! = nil) { hash_displacement++; // Move to the next position and try again to insert. // Here entry->mask value, must be: 0x111, 0x1111, 0x11111... Index = (index+1) &entry ->mask; If (index == begin) bad_weak_table(entry); // If (index == begin) bad_weak_table(entry); } if (hash_displacement > entry->max_hash_displacement) {if (hash_displacement > entry->max_hash_displacement) { Entry ->max_hash_displacement = hash_displacement; Weak_referrer_t &ref = entry->referrers[index]; Num_refs ref = new_referrer; entry->num_refs++; }Copy the code

At this point, the weak reference object has been successfully added.

Remove weak reference objects

Let’s see how the weak reference mechanism works when an object is released.

rootDealloc

RootDealloc logic:

Check whether an object uses a Tagged Pointer count. If so, no destructor is performed.

Next, determine whether the following conditions are met

  • Object is optimizedisaCounting mode (isa.nonpointer)
  • The object is notweakReference (! isa.weakly_referenced)
  • No associated object (! isa.has_assoc)
  • There is no customC++Destruction method (! isa.has_cxx_dtor)
  • Don’t usesideTableTo do reference counting (! isa.has_sidetable_rc)

If you do, you can use the function free(this) to quickly free memory.

The rest, go inobject_dispose((id)this)Slow release branch.

object_disposecallobjc_destructInstance

objc_destructInstancecallclearDeallocating

clearDeallocatingcallclearDeallocating_slow

weak_clear_no_lock

ClearDeallocating_slow calls weak_clear_NO_lock to clean up weak_table and sets all weak references to nil

void weak_clear_no_lock(weak_table_t *weak_table, Id referent_id) {// Weak reference object 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; Referrer = referrers[I]; If (referrer) {// If it is indeed the object to be released, null 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); weak_entry_remove(weak_table, entry); }Copy the code

At this point, the removal process is complete

Five, the summary

Finally, draw a few sketches as a summary

The underlying structure

Add the process

Remove the process