Memory management plays a very important role in APP development. In iOS, the system provides us with ARC development environment, which helps us do a lot of memory management content. In this chapter, we first take a look at how weak is implemented at the bottom level

Weak effect

__strong, __weak, and __unsafe_unretained can be used as an example 🌰.

After the temporary scope ends, the generated object is destroyed. We hold the object in the modifier outside the scope

NSLog(@" temporary scope start "); { LGPerson *person = [[LGPerson alloc] init]; NSLog(@"person object: %@", person); } NSLog(@" temporary end of scope "); * * * * * * * * * * * * * * * * * * * * * * * * * * * printing result * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 2020-01-19 10:57:13. 910542 + 0800 objc - debug (74175-19740208) 2020-01-19 10:57:13.911181+0800 objC-debug [74175:19740208] person object: 0x10221C900 > 2020-01-19 10:57:13.911277+0800 objC-debug [74175:19740208] LGPerson -[LGPerson dealloc] 2020-01-19 10:57:13.911367+0800 OBJC-DEBUG [74175:19740208] Temporary scope endsCopy the code

Take a look at the __strong modifier. You can see that the decorated object is not destroyed after the scope ends, indicating that the object’s reference count has increased

__strong LGPerson *strongPerson; NSLog(@" temporary scope start "); { LGPerson *person = [[LGPerson alloc] init]; NSLog(@"person object: %@", person); strongPerson = person; } NSLog(@" temporary end of scope "); NSLog (@ strongPerson: "% @", strongPerson); * * * * * * * * * * * * * * * * * * * * * * * * * * * printing result * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 2020-01-19 11:54:44. 079292 + 0800 objc - debug (74452-19777011) 2020-01-19 11:54:44.080060+0800 objC-debug [74452:19777011] person object: <LGPerson: 0x1019450S > 2020-01-19 11:54:44.080172+0800 OBJC-DEBUG [74452:19777011] Temporary scope End 2020-01-19 11:54:44.080292+0800 Objc-debug [74452:19777011] strongPerson: <LGPerson: 0x1019450s >Copy the code

Look again at the result of the __weak modifier. The __weak modifier does not increase the reference count, and the scope ends. After the object is freed, the modified object is nil. It does not cause the crash of wild Pointers, so it is a safe scheme

__weak LGPerson *weakPerson; NSLog(@" temporary scope start "); { LGPerson *person = [[LGPerson alloc] init]; NSLog(@"person object: %@", person); weakPerson = person; } NSLog(@" temporary end of scope "); NSLog (@ weakPerson: "% @", weakPerson); * * * * * * * * * * * * * * * * * * * * * * * * * * * printing result * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 2020-01-19 11:58:08. 842409 + 0800 objc - debug (74479-19780263) 2020-01-19 11:58:8.843151 +0800 objC-debug [74479:19780263] person object: <LGPerson: 0x101712030> 2020-01-19 11:58:08.843382+0800 objC-debug [74479:19780263] LGPerson -[LGPerson dealloc] 2020-01-19 11:58:08.843572+0800 objC-Debug [74479:19780263] Temporary scope end 2020-01-19 11:58:08.843762+0800 objC-Debug [74479:19780263] WeakPerson: (null)Copy the code

Finally, let’s take a look at the differences between __unsafe_unretained and __unretained. We can see from the results that the object is destroyed when the scope is gone, and a crash EXC_BAD_ACCESS of wild Pointers occurs when the decorated object is printed out of scope

The difference between __weak and __unsafe_unretained is that __weak is automatically set to nil upon release, but __unsafe_unretained is not.

__unsafe_unretained LGPerson *unsafePerson; NSLog(@" temporary scope start "); { LGPerson *person = [[LGPerson alloc] init]; NSLog(@"person object: %@", person); unsafePerson = person; } NSLog(@" temporary end of scope "); NSLog (@ unsafePerson: "% @", unsafePerson); * * * * * * * * * * * * * * * * * * * * * * * * * * * printing result * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 2020-01-19 12:02:34. 428120 + 0800 objc - debug (74513-19785153) 2020-01-19 12:02:34.428813+0800 objC-debug [74513:19785153] person object: 0x1019159F0 > 2020-01-19 12:02:34.428901+0800 objC-debug [74513:19785153] LGPerson -[LGPerson dealloc] 2020-01-19 12:02:34.429015+0800 OBJC-DEBUG [74513:19785153] Temporary scope endsCopy the code

summary

  • __strongModify after the objectReference countingWill increase, will not be destroyed outside the field
  • __weakAfter modification, the objectReference countingDoes not increase and is automatically set to outside the scopenil
  • __unsafe_unretainedAfter modification, the reference count is not increased and is not null out of scope, causing wild Pointers to crash

Through the above example, we have a basic understanding of the function of __weak. Then how to create and destroy __weak? The following is a deep exploration through the source code

The creation of a weak

Again using the previous example, directly trace assembly and symbolic breakpoints, found the underlying library called the objc_initWeak function

objc_initWeak

Two of the parameters location and newObj have the following meanings

  • locationSaid:__weakThe address of the pointer, as in the exampleweakPointer to address:&weakObjc. It is the address of a pointer. The reason why we want to store the address of the pointer is because at the end we’re going to talk about__weakThe contents of the pointer are set tonilYou can’t do this if you store only Pointers.
  • newObj: The object referenced in the exampleperson 。
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

storeWeak

Looking at the storeWeak source code, according to the comments, you can see the following points

  • HaveOld:weakWhether the pointer has already pointed to a weak reference
  • HaveNew:weakWhether the pointer needs to point to a new reference
  • CrashIfDeallocating: Indicates whether to weakly reference an object when it is being destructedcrash.
// Update a weak variable.
// If HaveOld is true, the variable has an existing value 
// that needs to be cleaned up. This value might be nil.
// If HaveNew is true, there is a new value that needs to be 
// assigned into the variable. This value might be nil.
// If CrashIfDeallocating is true, the process is halted if newObj is 
// deallocating or newObj's class does not support weak references.
// If CrashIfDeallocating is false, nil is stored instead.
enum CrashIfDeallocating {
    DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
};
template <HaveOld haveOld, HaveNew haveNew,
          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;// Acquire locks for old and new values.
    // Order by lock address to prevent lock ordering problems. 
    // Retry if the old value changes underneath us.Retry: ✅// If the weak pointer weakly referenced an obj before, remove the SideTable corresponding to obj and assign it to oldTable
    if (haveOld) {
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        // oldTable = nil if there is no weak reference
        oldTable = nil;
    }
    ✅// If the weak pointer wants to weakly reference a new obj, remove the SideTable corresponding to the obj and assign it to newTable
    if (haveNew) {
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }
    ✅// Lock operation to prevent multi-thread contention
    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
    ✅// Location should be consistent with oldObj. If it is different, the current location has already been processed by oldObj but has been modified by another thread
    if(haveOld && *location ! = oldObj) { SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);goto retry;
    }

    // Prevent a deadlock between the weak reference machinery
    // and the +initialize machinery by ensuring that no 
    // weakly-referenced object has an un-+initialized isa.
    if (haveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        ✅// If CLS is not already initialized, initialize first and then try to set weak references
        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 after initialization is complete
            previouslyInitializedClass = cls;
            ✅// Get newObj again after newObj is initialized
            gotoretry; }}// Clean up old value, if any.
   ✅Weak_unregister_no_lock is called to remove the weak pointer address from weak_entry_t of oldObj. Weak_entry_t weak_entry_t is weak_unregister_no_lock. Weak_entry_t is weak_entry_t
    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    // Assign new value, if any.
    ✅// If the weak pointer needs a weak reference to the new object newObj
    if(haveNew) {✅// Call weak_register_no_lock to record the address of weak pointer to weak_entry_t corresponding to newObj
        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.
        ✅// Update weakly_referenced bit flag bit of newObj's ISA pointer
        if(newObj && ! newObj->isTaggedPointer()) { newObj->setWeaklyReferenced_nolock(); }// Do not set *location anywhere else. That would introduce a race.
        ✅// * Location assigns the weak pointer directly to newObj and does not add +1 to newObj's reference count
        *location = (id)newObj;
    }
    else {
        // No new value. The storage is not changed.
    }
    
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

    return (id)newObj;
}
Copy the code

Because we’re calling it for the first time, we have a new object, in the case of haveNew, and we get a new hash table, SideTable, which basically uses the Weak_register_NO_lock method to insert.

weak_register_no_lock

Next, let’s look at how weak_register_no_lock registers weak references.

We found that the main preconditions inside the function, such as isTaggedPointer and DealLocating, are things where weak references are not possible.

If weak_entry_t can be weakly referenced, the corresponding Weak_entry_t will be removed from the Weak_entry_t hash array in the Weak_table where weak_entry_t is located. If weak_entry_t does not exist, a new weak_entry_t will be created. The referrer pointer to the weakly referenced object’s address is then inserted into the corresponding Weak_entry_t reference array through the append_referrer function. This completes the weak reference.

id weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, bool crashIfDeallocating)
{✅// Get the weakly referenced object first
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;
    ✅// If the referent of the weakly referenced object is nil or the TaggedPointer count is used, the value is returned
    if(! referent || referent->isTaggedPointer())return referent_id;

    // ensure that the referenced object is viable
    ✅// Make sure the referenced object is available (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, it cannot be weakly referenced
    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
    ✅// Find weak_entry corresponding to weak-referenced object referent in weak_table, and add the referrer to weak_entry
    weak_entry_t *entry;
    if((entry = weak_entry_for_referent(Weak_table, referent))) {✅// If weak_entry can be found, insert the referrer into weak_entry
        append_referrer(entry, referrer);
    } 
    else{✅// If weak_entry cannot be found, create a new weak_entry
        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

append_referrer

This step is mainly to find the corresponding Weak_entry hash array of weak reference objects, which is basically a traversal insertion process, the principle is relatively simple

static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{✅// If Weak_entry uses the static array inline_referrers
    if (! entry->out_of_line()) {
        // Try to insert inline.
        ✅// Try inserting 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; }}// Couldn't insert inline. Allocate out of line.
        ✅// If the inline_referrers location is already full, then it is converted to the referrers 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;
    }

    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 length, and insert the referrer into the array
    if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
        return grow_refs_and_insert(entry, new_referrer);
    }
    ✅// If expansion is not required, directly insert weak_entry into weak_entry
    ✅// & (entry->mask) Ensure that the position of begin is no longer 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

weak_unregister_no_lock

If the weak pointer weakly refers to another object before pointing to OBj, you need to remove the weak pointer from the weak_entry_t hash array of the other object. In storeWeak method will call weak_unregister_NO_lock function to do the removal operation, let’s take a look at weak_unregister_no_lock function source code

Weak_unregister_no_lock function will first find the weak_entry_t corresponding to the weakly referenced object referent in weak_table, and remove the weakly referenced object referrer in weak_entry_t. After removing the element, determine whether there are still elements in weak_entry_t at this time. If weak_entry_t has no elements at this point, you need to remove weak_entry_t from Weak_table.

void weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, 
                        id *referrer_id)
{✅// Get the weakly referenced object and its address
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;

    weak_entry_t *entry;

    if(! referent)return;
    ✅// Find weak_entry_t corresponding to weak_entry_t of the previously weakly referenced object referent
    if((entry = weak_entry_for_referent(Weak_table, referent))) {✅// Remove the weak-reference referrer from the hash array of weak_entry_t corresponding to the weak-reference object referent
        remove_referrer(entry, referrer);
        ✅// After removing the element, check whether the Weak_entry_t hash array 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 the hash array of weak_entry_t is empty, you need to remove weak_entry_t 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

At this point, the weak reference process for an object is complete

The destruction of weak

The class dealloc automatically nulls the weak reference object when it is out of scope

_objc_rootDealloc

- (void)dealloc { _objc_rootDealloc(self); } ********************************** void _objc_rootDealloc(id obj) { assert(obj); obj->rootDealloc(); } * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * the inline void objc_object: : rootDealloc () {/ / ✅ if it is Tagged Pointer, If (isTaggedPointer()) return; // fixme necessary? /* ✅ if (1) the isa is optimized, (2) the weak pointer is not referenced, (3). Free () */ 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) should be disposed as object_dispose((id)this); }}Copy the code

We obviously do not meet the above conditions here because we weakly referenced object_Dispose and continued to follow up

object_dispose

Objc_destructInstance is called in the object_dispose function

id 
object_dispose(id obj)
{
    if(! obj)return nil;

    objc_destructInstance(obj);    
    free(obj);

    returnnil; } * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *Copy the code

objc_destructInstance

The weak reference is in clearDeallocating, as you can see that the C++ destructor is destroyed and the associated object is removed

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 there is a C++ destructor, run the related function from
        if (cxx) object_cxxDestruct(obj); 
        ✅// If there are any associated objects, remove all associated objects and remove themselves from the Association Manager map
        if (assoc) _object_remove_assocations(obj); 
        ✅// Continue to clean up other related references
        obj->clearDeallocating(); 
    }
    return obj;
}
Copy the code

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 has an optimized ISA reference count and has weak references or uses sideTable's auxiliary reference countclearDeallocating_slow(); } assert(! sidetable_present()); }Copy the code

clearDeallocating_slow

We now generally use optimized ISA reference counts, so let’s continue exploring for that purpose. We can see from the source code that the main operation is to find the corresponding SideTable, and then in the SideTable weak_table, the weak reference object is empty, the main method is weak_clear_NO_lock

NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
    assert(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));
    ✅// In the global SideTables, find the corresponding SideTable using the this pointer (the object to release) as the key
    SideTable& table = SideTables()[this];
    table.lock();
    if (isa.weakly_referenced) {
        ✅Weak_clear_no_lock Sets the weak-reference pointer to the object to be freed to nil using the Weak_clear_no_lock function
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    ✅// Use sideTable's auxiliary reference count to erase the object's reference count directly from sideTable
    if (isa.has_sidetable_rc) {
        table.refcnts.erase(this);
    }
    table.unlock();
}
Copy the code

weak_clear_no_lock

We can see through the source code, this method is similar to the insert method, is to find the corresponding eak_entry_t array, and then traverse to find the corresponding pointer address, and then set to nil, to prevent the error of wild pointer

void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{✅// Get the address of the weakly referenced object
    objc_object *referent = (objc_object *)referent_id;
    ✅Weak_entry_t in weak_table according to object address find weak_entry_t in weak_table
    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;
    
    ✅// Find an array of weak pointer addresses that weakly reference this object
    if (entry->out_of_line()) {
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    } 
    else {
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }
    ✅// return the address of each weak pointer
    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 logic 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

At this point, the destruction of a weak reference is also complete and set to nil automatically

conclusion

  • When an object obj is pointed to by the weak pointer, the weak pointer is stored to obj as its keysideTableOf the classweak_tableThat’s the hash tableWeak pointer arrayThe inside.
  • When an object objdeallocWhen the method is called, the Runtime will take obj as the key fromsideTabletheweak_tableIn the hash table, find the correspondingWeak pointer listTable, and then set the weak Pointers inside tonil.

Create a process diagram

Create a process summary:

The Runtime maintains a weak reference table that stores all pointer addresses of weak references to OBj in weak_entry_t corresponding to OBj.

  1. The global hash table is first foundSideTablesThe weak reference table corresponding toweak_table
  2. inweak_tableOf the weakly referenced object inreferentAnd create or insert the correspondingweak_entry_t
  3. thenappend_referrer(entry, referrer)I’m going to add my new weak reference objectentry
  4. The lastweak_entry_insert 把entryJoin usweak_table

Schematic diagram of destruction process

Summary of destruction process:

  1. We first get an array of all weak pointer addresses based on the object address
  2. And then I’m going to go through the array and I’m going to empty out the corresponding datanil
  3. At the same time, willweak_entry_tRemove the weak reference tableweak_table.

reference

IOS basic principle exploration — weak implementation principle

Memory management – Weak pointer implementation principle