In normal development, we usually use weak to break circular references and avoid memory leaks. The weak keyword is used for weak references. The retainCount reference counter of the modified object does not increment by 1 and is automatically set to nil when the reference object is released. In this chapter, we will explore the implementation principle of Weak from the bottom.

I’m weak at first

#import <Foundation/Foundation.h>
#import "WYPerson.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        WYPerson *person =[[WYPerson alloc]init];
        id __weak weakPerson = person;
        
    }
    return 0;
}
Copy the code

We target objc_initWeak by tracing the assembly and the undersign breakpoint.

2: weak Created

1. objc_initWeak

As usual, look at the definition of objc_initWeak

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
  1. Location:__weakThe address of the pointer, as in the exampleweakPointer to address:&weakPerson, which is the address of a pointer.
  2. NewObj: The referenced object, in the exampleperson

2. storeWeak

Note: the following code has been removed some non-key comments, need to see the complete code friends can look at the source code

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) {oldObj = *location; // If (haveOld) {oldObj = *location; oldTable = &SideTables()[oldObj]; } else {oldTable = nil oldTable = nil; If (haveNew) {newTable = &sidetables ()[newObj];} 🌹// 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; } if (haveNew && newObj) { Class cls = newObj->getIsa(); 🌹// If CLS is not already initialized, initialize first and then try to set weak reference if (CLS! = previouslyInitializedClass && ! ((objc_class *)cls)->isInitialized()) { SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); _class_initialize(_class_getNonMetaClass(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. 🌹 / / mark previouslyInitializedClass = CLS after initialization; 🌹// Obtain newObj goto retry after newObj is initialized. }} // Clean up old value, if any. 🌹// Weak_unregister_no_lock, weak_unregister_lock, weak_unregister_no_lock, Weak_entry_t if (haveOld) {weak_unregister_no_lock(&oldTable-> Weak_table, oldObj, location); weak_entry_t if (haveOld) {weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); } // Assign new value, if any. 🌹// If weak pointer needs weak reference to new object newObj if (haveNew) {🌹// Call weak_register_no_lock method, Weak_entry_t = (objc_object *) Weak_register_no_lock (&newTable-> Weak_table, (id)newObj, weak_entry_t = (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. 🌹// Update weakly_referenced bit flag bit if (newObj &&! newObj->isTaggedPointer()) { newObj->setWeaklyReferenced_nolock(); } // Do not set *location anywhere else. That would introduce a race. 🌹 And newObj's reference count +1 *location = (id)newObj; } else { // No new value. The storage is not changed. } SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); return (id)newObj; }Copy the code
  1. The HaveOld: weak pointer has previously pointed to a weak reference
  2. HaveNew: Weak Whether the weak pointer needs to point to a new reference
  3. CrashIfDeallocating: Whether to crash if the object being weakly referenced is being destructed.

Here we see a SideTable involved in the code, which you can see in this article. SideTable is also a structure type at the bottom, while weak objects are managed by weak_table, which is a weak_reference table with type Weak_table_T.

struct SideTable { spinlock_t slock; SideTable RefcountMap refcnts; SideTable RefcountMap refcnts; Weak_table_t weak_table; // Weak reference table...... };Copy the code

Weak_table_t is also a structure, where weak_entry_t is also posted

struct weak_table_t { weak_entry_t *weak_entries; size_t num_entries; uintptr_t mask; uintptr_t max_hash_displacement; }; struct weak_entry_t { DisguisedPtr<objc_object> referent; union { struct { weak_referrer_t *referrers; uintptr_t out_of_line_ness : 2; uintptr_t num_refs : PTR_MINUS_2; uintptr_t mask; 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]; }; }; . };Copy the code

aboutweak_table_t:

  1. Weak_entries: Hash array, used to store information about weak reference objects. Weak_entry_t
  2. Num_entries: number of hash array elements
  3. Mask: the hash array length is -1, which will participate in hash calculation. Note that this is the length of the hash array, not the number of elements. For example, the array might be 64 in length and only have 2 elements.)
  4. Max_hash_displacement: Maximum number of hash conflicts that can occur to determine if a logical error has occurred (the number of conflicts in the hash table will never exceed the changed value)

aboutweak_entry_t:

  1. referrers: is it a point toweakAll variables of the object.
  1. referent: It is in memoryweakObject.

Having looked at the relevance of SideTable, here’s a quick summary:

  1. Weak table is a weak reference table, implemented as a Weak_table_t structure, which stores all weak reference information related to all objects.
  2. Weak_entry_t is an internal structure stored in a weak-reference table that is responsible for maintaining and storing all weak-reference hash tables that point to an object.
  3. Referrers in Weak_entry_t stores all variables pointing to the weak object.

Maybe it’s a little bit more intuitive to draw a picture of this relationship

photo

Back to storeWeak, we first go to weak_register_no_lock to add weak references.

3. weak_register_no_lock

Some notes have been added

id weak_register_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id, Bool crashIfDeallocating) {🌹// First get the object objc_Object *referent = (objc_object *) Referent_id; objc_object **referrer = (objc_object **)referrer_id; 🌹// If the referent is nil or TaggedPointer is used, return if (! referent || referent->isTaggedPointer()) return referent_id; // Ensure that the referenced object is viable 🌹// Ensure that the referenced object is viable (there is no destructor, and weak references should be supported) 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 the object is being destructed, Locating 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 { return nil; }} // Now remember it and where it is being stored 🌹// Find weak_entry corresponding to weak_referent in weak_table, and add the referrer to weak_entry Weak_entry weak_entry_t * entry; if ((entry = weak_entry_for_referent(weak_table, Append_entry (entry, referrer))) {🌹// if weak_entry can be found, insert weak_entry (entry, referrer); } else {🌹// If weak_entry cannot be found, create a new 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

This method takes four arguments, which also appear above:

  1. Weak_table: Weak_table_t structure type of the global weak reference table.
  2. Referent_id: weak pointer.
  3. *referrer_id: weak pointer address.
  4. CrashIfDeallocating: Whether the weakly referenced object should crash if it is being destructed.

Weak_register_no_lock process summary:

  1. If referent is nil or referent
  2. TaggedPointer is used and returns directly without doing any operation.
  3. Throws an exception if the object is being destructed.
  4. If the object cannot be referenced by weak, return nil.
  5. If the object is not redestructed and can be weakly referenced, the weak_entry_for_referent method is called to find the corresponding Weak_entry from the weak-reference table based on the weak-reference object’s address
    1. If found, the append_referrer method is called to insert the weak pointer address into it.
    2. Otherwise, create a Weak_entry.

4. append_referrer

The source code has been annotated in part

static void append_referrer(weak_entry_t *entry, Objc_object **new_referrer) {🌹// If weak_entry uses static array inline_referrers if (! Entry ->out_of_line()) {// Try to insert inline-🌹 // Try to insert the referrer into the array for (size_t I = 0; i < WEAK_INLINE_COUNT; i++) { if (entry->inline_referrers[i] == nil) { entry->inline_referrers[i] = new_referrer; return; }} // insert inline.allocate out of line. 🌹// If inline_referrers is used up, 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_ness = REFERRERS_OUT_OF_LINE; entry->mask = WEAK_INLINE_COUNT-1; entry->max_hash_displacement = 0; } 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, expand the array space to double the current size, Then insert the referrer into the array if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {return grow_refs_and_insert(entry, new_referrer); } 🌹// If capacity expansion is not required, Directly insert into weak_entry 🌹// & (entry->mask) Ensure that the position of BEGIN can only be greater than or equal to the length of the array size_t begin = w_hash_pointer(new_referrer) & (entry->mask); size_t index = begin; size_t hash_displacement = 0; while (entry->referrers[index] ! = nil) { hash_displacement++; index = (index+1) & entry->mask; if (index == begin) bad_weak_table(entry); } 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++; }Copy the code

Append_referrer finds the weak_entry hash array corresponding to the weak reference object and iterates over the insert, along with expansion of course

5. weak_unregister_no_lock

If the weak pointer had previously pointed to a weak reference, this method is called to remove the old weak pointer address.

void weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, Objc_object *referent = (objc_object *)referent_id; objc_object **referrer = (objc_object **)referrer_id; weak_entry_t *entry; if (! referent) return; 🌹// Find the previous weak reference object referent corresponding weak_entry_t if ((entry = weak_entry_for_referent(weak_table, Referent))) {🌹// Remove the weak reference referrer from the hash array of weak_entry_t corresponding to weak_referent; 🌹// After removing the element, check whether the hash array of weak_entry_t is empty 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 weak_entry_t hash array is empty, Weak_entry_t remove from weak_table if (empty) {weak_entry_remove(weak_table, entry); } } // Do not set *referrer = nil. objc_storeWeak() requires that the // value not change. }Copy the code
  • inweak_tableTo find the object that was weakly referenced beforereferentThe correspondingweak_entry_tIn theweak_entry_tRemoves the weakly referenced object from thereferrer.
  • After the element is removed, determine whenweak_entry_tIs there any element left in.
  • If at this timeweak_entry_tIf there are no more elements, you need to setweak_entry_tfromweak_tableRemoved.

Weak Release

Weak dealloc dealloc dealloc dealloc dealloc dealloc dealloc dealloc

1. dealloc

- (void)dealloc { _objc_rootDealloc(self); } void _objc_rootDealloc(id obj) { ASSERT(obj); obj->rootDealloc(); } 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

Let’s first look at the criteria in the code:

  1. Is the optimized ISA,
  2. Not referenced by the weak pointer,
  3. No associated object,
  4. No C++ destructor,
  5. There is no sideTable,

Weak dispose directly object_dispose

2. object_dispose

Objc_destructInstance is called internally

id object_dispose(id obj) { if (! obj) return nil; objc_destructInstance(obj); free(obj); return nil; } * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *Copy the code

3. objc_destructInstance

void *objc_destructInstance(id obj) { if (obj) { // Read all of the flags at once for performance bool cxx = obj->hasCxxDtor(); bool assoc = obj->hasAssociatedObjects(); If (CXX) object_cxxDestruct(obj); // This order is important. 🌹 // if there is a C++ destructor, run the related function if (CXX) object_cxxDestruct(obj); 🌹// Remove all associated objects, if any, and remove themselves from the Association Manager map if (ASsoc) _object_remove_ASsocations (obJ); 🌹// Continue cleaning up other related references obj->clearDeallocating(); } return obj; }Copy the code

The removal of the associated object happens here, and the weak reference is in clearDeallocating

4. clearDeallocating

inline void objc_object::clearDeallocating() { if (slowpath(! Isa.nonpointer)) {// Slow path for raw pointer Isa.🌹 // If the object to be released does not have an optimized ISA reference count, sidetable_clearDeallocating(); } else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) { // Slow path for non-pointer isa with weak refs And /or side table data. 🌹// If the object to be released uses an optimized ISA reference count and has weak references or uses sideTable's auxiliary reference count clearDeallocating_slow(); } assert(! sidetable_present()); }Copy the code

The typical object is an optimized ISA, which typically goes to clearDeallocating_slow

5. clearDeallocating_slow

NEVER_INLINE void objc_object::clearDeallocating_slow() { assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc)); 🌹// SideTables() SideTables = SideTables()[this]; table.lock(); If (isa.weakly_referenced) {🌹// The object to be released is weakly referenced, Weak_clear_no_lock (&table. Weak_no_lock, (id)this); Weak_clear_NO_lock (&table. Weak_table, (id)this); weak_clear_no_lock(&table. } 🌹// if (isa.has_sidetable_rc) {table.refcnt.erase (this); if (isa.has_sidetable_rc) {table.refcnt.erase (this); } table.unlock(); }Copy the code

In SideTable, find the corresponding SideTable, and then in SideTable’s Weak_table, empty the weak reference object. The main method is Weak_clear_NO_lock.

6. weak_clear_no_lock

void weak_clear_no_lock(weak_table_t *weak_table, Objc_object *referent = (objc_object *)referent_id; 🌹// According to the object address to find the weakly referenced object referent in weak_table corresponding weak_entry_t 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 the weak pointer does weakly reference the object referent, set the weak pointer to nil if (*referrer == referent) {*referrer = nil; } 🌹// If the stored weak pointer does not have a weak reference object referent, this may be due to a logical error in the Runtime code, 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

Obtain the address of weak reference object, and then find the corresponding eak_entry_t array in the corresponding Weak_table, and then find the corresponding pointer address through traversal, and directly set it to nil in the simplest and crude way to prevent wild Pointers.

Four: Flow chart

Weak create flow chart

Weak creates a summary

  1. Weak_table is a weak reference table corresponding to the global hash table SideTables.
  2. In weak_table, the referent of the object is weakly referenced, and the corresponding Weak_entry_t is created or inserted.
  3. Then 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.

Weak destruction flowchart

Weak destroy summary

  1. Gets the address of a weakly referenced object
  2. Find the corresponding corresponding correspondingweak_tableIn theeak_entry_tAn array of
  3. Find the corresponding pointer address by traversing
  4. Set it to nil the simplest and most crude way to prevent wild Pointers.

Five: reference

SideTables, SideTable, weak_table, weak_entry_t

Explore the implementation of weak from the source code

The weak principle of memory management is explored