Find the weak variable initialization entry

In main.m, write the following code at the end of the function break point and turn on assembly mode: debug-> Debug Workflow -> Alway show Disassembly.

#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        id obj = [NSObject new];
        id obj2 = [NSObject new];
        printf("Start tag\n");
        {
            __weak id weakPtr = obj; // Call objc_initWeak to initialize the weak variable
            weakPtr = obj2; // Change the weak variable pointing to
        } 
        // With the right curly brace, call objc_destroyWeak to destroy the weak variable
        // The weak variable is destroyed, but the weak variable is not destroyed.
        
        printf("End tag\n"); // ⬅️ breakpoint is typed here
    }
    return 0;
}
Copy the code

When command + R is executed, it will enter a breakpoint. Here we only focus on the middle part of the Start tag and End tag.

.0x100000c97 <+87>:  callq  0x100000e06               ; symbol stub for: objc_initWeak ⬅️ The weak variable is initialized0x100000c9c <+92>:  movq   -0x20(%rbp), %rsi
0x100000ca0 <+96>:  leaq   -0x18(%rbp), %rdi
0x100000ca4 <+100>: callq  0x100000e1e               ; symbol stub for: objc_storeWeak ⬅️ Changes the direction of the weak variable0x100000ca9 <+105>: leaq   -0x18(%rbp), %rdi
0x100000cad <+109>: callq  0x100000e00               ; symbol stub for: objc_destroyWeak ⬅️ weak variable destroy...Copy the code

The callq instruction represents the function call, and the function associated with the weak variable is: Objc_initWeak, objc_storeWeak, and objc_destroyWeak initialize the weak variable, assign the weak variable (modify the pointer to it), and destroy the weak variable respectively.

Initialize the weak variable in objc4-781. Search objc_initWeak globally. In objc-internal.

OBJC_EXPORT id _Nullable 
objc_initWeak(id _Nullable * _Nonnull location, id _Nullable val)
    OBJC_AVAILABLE(10.7.5.0.9.0.1.0.2.0);
Copy the code

ARC and weak keywords are all released after iOS 5.0. The objc_initWeak function implementation is found in the nsobject. mm file.

objc_initWeak

Initialize a fresh weak pointer to some object location. It would be used for code like:

(The nil case) 
__weak id weakPtr; 
(The non-nil case) NSObject *o = ... ; __weak id weakPtr = o;Copy the code

This function IS NOT thread-safe with respect to concurrent modifications to the weak variable. (Concurrent weak clear is safe.)

Initialize a new weak Pointer to an object location (when an old weak pointer is assigned: the current pointer is cleaned up first). This function is not thread-safe for concurrent modification of the weak variable. Weak clear concurrency is thread safe. Weak clear concurrency is thread safe.

// Template parameters
// Keep these enumerations in mind,
// HaveOld if true, the __weak variable is currently pointing to an object, otherwise, it can be a newly created __weak variable
// HaveNew If true, the object assigned to the right of the __weak variable has a value, otherwise the __weak variable will be referred to nil
enum HaveOld { DontHaveOld = false, DoHaveOld = true }; // Whether there is an old value
enum HaveNew { DontHaveNew = false, DoHaveNew = true }; // Whether there is a new value

/** * @param location Address of __weak PTR. // Address of the __weak variable (objc_object **) Struct objc_object *) * @param newObj Object PTR
id
objc_initWeak(id *location, id newObj)
{
    // If the object does not exist
    if(! newObj) {// *location = nil, which means to point __weak to nil, and return nil.
        *location = nil; 
        return nil;
    }
    
    // storeWeak is a template function. DontHaveOld means there are no old values, indicating that the __weak variable is newly initialized.
    // DoHaveNew indicates a new value, which is newObj
    // DoCrashIfDeallocating If newObj's ISA has been marked deallocating or the class newObj belongs to does not support weak references, crash

    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}
Copy the code

The objc_initWeak function takes two arguments:

  1. id *location:__weakThe address of the variable in the sample codeweakVariable fetch address:&weakPtr, it’s a pointer to a pointer, and the reason we want to store the address of the pointer is becauseweakAfter the object to which the variable points is released, theweakVariable Pointers are set to nil if only Pointers are stored (i.eweakThe address value to which the variable points cannot be set.

This reminds us that when we do something on a linked list, the function entry is a pointer to the head pointer of the list.

If you're not familiar with Pointers, you might be a little confused. Why do we use Pointers to Pointers? When we change the pointer to a function, we also change the pointer to an external pointer. Not really! It is important to note that when a function parameter is a pointer, it passes in an address, and then creates a temporary pointer variable inside the function that points to the address passed in. If you change the pointer, you only change the pointer to the temporary pointer variable inside the function. The external pointer variable has nothing to do with it, except that they both point to the same address initially. Everything we do to the contents of this address is reflected in a pointer variable to that address. This address is a pointer to the address, if there is no ` const ` restrictions, we can do it for the content of inside address any operation even contents the ballast zero, these operations are to the address of memory, no matter how the memory is there, it addresses here all the time, and our original pointer has been pointing to it, We need to set the pointer to nil if we know the address of the pointer itself. If we put '0x0' in the address of the pointer itself, we can set our pointer to nil!Copy the code
  1. id newObj: the object used in the sample codeobj.

This method returns the value of the storeWeak function: Weakly_referenced bit of OBj’s ISA (ISA_T) is set to 1, indicating that the object has weak references. When the object is destroyed, those weak references to it should be processed. This is where the mechanism for the weak variable to be set to nil is implemented.

See the implementation of objc_initWeak function, it is called storeWeak function internally, and the template parameter when executing is DontHaveOld (no old value), which means that weakPtr does not point to any object before, our weakPtr is just initialized, Of course, I’m not pointing to old values. What is involved here is that when the weak variable changes pointing, the address of the weak variable is removed from the hash array of weak_entry_t of the object to which it previously pointed. DoHaveNew indicates a new value.

The storeWeak function implements the following core functions:

  • willweakAddress of variablelocationdepositobjThe correspondingweak_entry_tHash array (or fixed length is4Is used in theobjAll of the hash array is found through the destructorweakThe address of the variable willweakThe address to which the variable points (*location) tonil.
  • If it’s enabledisaOptimization, then willobjisa_tweakly_referencedThe position is 1, which is used to identifyobjThere areweakReferences. When objectdeallocWhen,runtimeDepending on theweakly_referencedFlag bit to determine if a search is neededobjThe correspondingweak_entry_tAnd sets all its weak references tonil.

__weak ID weakPtr = obj A complete vernal understanding is: Take weakPtr address and obj, call objc_initWeak function, add weakPtr address to weak_entry_t hash array of weak_reference hash table of OBJC, And assign the address of obj to *location (*location = (id)newObj), then set weakly_referenced field of OBj’s ISA to 1, and finally return OBj.

From the storeWeak function implementation will be associated with our previous several articles, think about some exciting 😊.

storeWeak

Analysis of storeWeak function source implementation:

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.

Update an weak variable. If HaveOld is true, the weak variable has an existing value that needs to be cleared. This value can be nil. If HaveNew is true, a new value needs to be assigned to the weak variable. This value can be nil. If CrashIfDeallocating is true, the program will crash if newObj’s ISA has been marked deallocating or if the class newObj belongs to does not support weak references. If CrashIfDeallocating is false, then the problem above is simply storing nil in the weak variable.

// DoCrashIfDeallocating: If newObj's ISA has been marked deallocating, or the class newObj belongs to doesn't support weak references, the function will crash,
DontCrashIfDeallocating: Does not crash, and places *location = nil
enum CrashIfDeallocating {
    DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
};

// Template parameters.
enum HaveOld { DontHaveOld = false, DoHaveOld = true }; // Whether there is an old value
enum HaveNew { DontHaveNew = false, DoHaveNew = true }; // Whether there is a new value

/ / ASSERT (haveOld | | haveNew) ASSERT macro definition, when does not meet the conditions of in brackets is executed assertion,
// If the parentheses are false, the assertion is executed. If the parentheses are true, the function is executed further.
Swift-like guard statements, which execute the following function if true and return if false.

template <HaveOld haveOld, HaveNew haveNew,
          CrashIfDeallocating crashIfDeallocating>
static id
storeWeak(id *location, objc_object *newObj)
{
    // If haveOld is false and haveNew is false, meaning that there is neither new nor old value, then the assertion is executed
    ASSERT(haveOld || haveNew);
    
    If you start with no new value and your newObj == nil has no new value, then you can execute the function normally, otherwise crash is declared.
    if(! haveNew)ASSERT(newObj == nil);

    // pointer to objc_class to newObj's Class, indicating that newObj's Class has been initialized
    Class previouslyInitializedClass = nil;
    
    // The old object to which the __weak variable was pointed
    id oldObj;
    
    // When did the object go into the SideTable?
    
    // SideTable where the old value object is located
    SideTable *oldTable;
    
    // SideTable where the new value object is located
    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. 
    
    // Get the spinlock_t in the SideTable where the old and new values are. (SideTable - > slock)
    // Sort the above two locks according to their addresses to prevent the occurrence of lock sorting problems.
    // Retry if the old value is changed during the execution of the following function.
    // The goto statement can be executed directly at the specified location.
 retry:
    if (haveOld) { 
        // If there is an old value, this old value represents the object to which the passed weak variable is currently pointing.
        // Dereference (*location) to oldObj.
        oldObj = *location;
        
        // Get the SideTable where the old value is
        oldTable = &SideTables()[oldObj];
    } else {
        // If weak PRT currently points to no other object, oldTable is assigned nil.
        oldTable = nil;
    }
    
    if (haveNew) {
        // Get the SideTable where newObj is located
        newTable = &SideTables()[newObj];
    } else {
        // There is no new value, newObj is nil, newTable is also assigned nil
        newTable = nil;
    }

    Sidetables oldTable and newTable are locked according to haveOld and haveNew
    
    // Lock operation to prevent multi-thread contention
    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);

    // *location should be the same as oldObj. If it is different, *location was modified by another thread before the lock was added
    if(haveOld && *location ! = oldObj) {// Unlock, jump to Retry and retry the function
        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.
    
    // Make sure that no weakly referenced objects have uninitialized ISA.
    // Prevent deadlocks between Weak Reference Machinery and +initialize Machinery.
    
    // There is a new value haveNew and newObj is not nil, check whether the class newObj belongs to is initialized.
    if (haveNew  &&  newObj) {
        // The class to which newObj belongs
        Class cls = newObj->getIsa(a);/ / initialized previouslyInitializedClass records are classes to prevent repetition
        if(cls ! = previouslyInitializedClass && ! ((objc_class *)cls)->isInitialized())
        { 
            // If CLS has not been initialized, initialize it first and then try to set weak.
            
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); / / unlock
            
            NewObjClass Initialize = newObjClass initialize = newObjClass initialize = newObjClass initialize
            class_initialize(cls, (id)newObj);

            // If this class is finished with +initialize then we're good.
            // If the class completes the +initialize initialization, this is a good result for us.
            
            // 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.
            
            // If the class completes the +initialize task in this thread, then this is fine.
            // If the class continues to execute the +initialize task in the thread,
            // (for example, an instance of this class is calling the storeWeak method, which calls +initialize.)
            // So we can continue, but above it will do initialization and not yet initialization checks.
            / / on the contrary, when the retry setting previouslyInitializedClass for newObj Class to identify it.
            // Instead set previouslyInitializedClass to recognize it on retry.
            / / the record previouslyInitializedClass here, prevent the if branch to enter again
            previouslyInitializedClass = cls;
            
            gotoretry; }}// Clean up old value, if any.
    // Clean up old values. If there are old values, perform weak_unregister_no_lock.
    
    if (haveOld) {
        // Remove location from the hash array of oldObj's Weak_entry_t
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    // Assign new value, if any.
    Weak_register_no_lock if there is a new value.
    
    if (haveNew) { 
        // Call weak_register_no_lock to record weak PTR's address into newObj's Weak_entry_t hash array
        // If newObj's ISA has been marked deallocating, or the class newObj belongs to does not support weak references, a crash will occur in the Weak_register_no_lock function
        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.
        
        // There are two cases of setting an object with weak references:
        // 1): Update weakly_referenced bit of newObj's ISA when an object's ISA is an optimized ISA.
        // 2): If an object's ISA is the original class pointer, its reference count and weak reference flag bits are included in the reference count in refCount. (Different bits represent different information)
        // The reference count value of the object (type size_t) needs to be found from refcount. The first digit of the reference count value identifies that the object has a weak reference (SIDE_TABLE_WEAKLY_REFERENCED).

        if(newObj && ! newObj->isTaggedPointer()) {
            Struct objc_objcet isa (ISA_t) uintptr_t Weakly_referenced: 1;
            // set the last bit of isa to 1 if isa isa raw pointer.
            newObj->setWeaklyReferenced_nolock(a); }// Do not set *location anywhere else. That would introduce a race.
        // Do not set *location anywhere else, it may cause a race.
        // *location is assigned, weak PTR refers directly to newObj, and you can see that newObj's reference count is not +1
        
        *location = (id)newObj;
    }
    else {
        // No new value. The storage is not changed.
        // No change occurs if there is no new value
    }
    
    Other threads can access oldTable and newTable
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
    
    Weakly_referenced Bit is set to 1 when newObj is compared with when weakly_referenced bit was just passed in. (If it started at 1, it doesn't change.)
    return (id)newObj;
}
Copy the code

storeWeakFunction flow chart:

The storeWeak function essentially takes five parameters, including HaveOld HaveOld, HaveNew HaveNew, and CrashIfDeallocating CrashIfDeallocating, which is a template enumeration. In fact, these are three bool arguments, and in the objc_initWeak function, the values of these three arguments are false, true, and true respectively, because the weak variable must have a new value, not an old value.

objc_storeWeak

The example code calls objc_storeWeak when we assign a new value to the __weak variable, so take a look at the source of the objc_storeWeak function.

This function stores a new value into a __weak variable. It would be used anywhere a __weak variable is the target of an assignment.

This function stores the new value in the __weak variable. The __weak variable can be used anywhere the assignment target is located.

id
objc_storeWeak(id *location, id newObj)
{
   // DoHaveOld true has old values
   // DoHaveNew true has a new value
   return storeWeak<DoHaveOld, DoHaveNew, DoCrashIfDeallocating>
       (location, (objc_object *)newObj);
}
Copy the code

Internal is also a direct call to storeWeak, DoHaveOld and DoHaveNew are both true, indicating that this time we need to deal with the current pointing of __weak variable (weak_unregister_no_lock), The __weak variable then points to the new object (weak_register_no_lock).

So far it’s clear that objc_initWeak is used to initialize the __weak variable, internally only weak_register_no_lock-related calls are required, and then when assigning to the __weak variable, Is to first deal with its pointing to the old value (Weak_unregister_NO_lock), and then deal with its new pointing. (weak_register_no_lock)

objc_destroyWeak

In the example code, the __weak variable, which is a local variable, ends its scope when the right curly brace is left and must be destroyed. In the assembly code, we see the objc_destroyWeak function called, which, according to its name, should be the function called when the __weak variable is destroyed. If the __weak variable is destroyed before the object to which it points, what happens to the address where the __weak variable is stored in the weak_entry_t hash array of the object to which it points? Take a look at the objc_destroyWeak function and you’ll find the answer.

Destroys the relationship between a weak pointer and the object it is referencing the internal weak table. If the weak pointer is not referencing anything, there is no need to edit the weak table. This function IS NOT thread-safe with respect to concurrent modifications to the weak variable. (Concurrent weak clear is safe.)

Destroys the relationship between an weak Pointer and the weak reference table of the object to which it points. (The hash array of an object’s Weak_entry_t holds the addresses of all weak references to the object, which means to remove the specified weak_entry_t address from the hash array of the object’s Weak_entry_t.) If weak Pointer does not point to anything, there is no need to edit the hash array of Weak_entry_t. This function is not thread-safe for concurrent modification of weak references. (Weak clear running concurrently is thread-safe)

/** * @param location The weak pointer address. // location is The address of The __weak variable (objc_object **) */
void
objc_destroyWeak(id *location)
{
    // The storeWeak function is called directly.
    // DoHaveOld true has old values
    // DontHaveNew false has no new value
    // DontCrashIfDeallocating false
    // location weak Specifies the address of the variable
    // nil newObjc is nil
    (void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
        (location, nil);
}
Copy the code

DoHaveOld has an old value, DontHaveNew has no new value, DontCrashIfDeallocating doesn’t need crash, and newObj is nil. The argument is only the location address of the weak reference to be destroyed. Recall the storeWeak function we analyzed in detail above:

.// Clean up old value, if any.
// If there are old values, weak_unregister_no_lock is used
if (haveOld) {
    // Remove location from the hash array of weak_entry_t for oldObj
    weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); }...Copy the code

Here is also clear, compared to the above initialization and assignment of the __weak variable, here is to do the destruction operation, just handle the old value, call weak_unregister_no_lock function is good.

Weak_unregister_no_lock function detailed analysis in “iOS weak underlying implementation principle (2) : objC-weak function list full analysis”.

Nsobject. mm file storeWeak function browse down, found two arguments only different internal call storeWeak factory function.

objc_storeWeakOrNil

This function store a new value into a __weak variable. If the new object is deallocating or the new object’s class does not support weak references, stores nil instead.

This function stores the new value in the __weak variable. If newObj’s ISA has been marked deallocating or the class newObj belongs to does not support weak references, the __weak variable points to nil.

id
objc_storeWeakOrNil(id *location, id newObj)
{
    return storeWeak<DoHaveOld, DoHaveNew, DontCrashIfDeallocating>
        (location, (objc_object *)newObj);
}
Copy the code

The only difference from objc_storeWeak is DontCrashIfDeallocating, if newObj’s ISA has been marked deallocating or the class newObj belongs to doesn’t support weak references, Then the __weak variable points to nil and crash does not occur.

objc_initWeakOrNil

id
objc_initWeakOrNil(id *location, id newObj)
{
    if(! newObj) {// If the new value does not exist, direct the __weak variable to nil
        *location = nil;
        return nil;
    }
    
    return storeWeak<DontHaveOld, DoHaveNew, DontCrashIfDeallocating>
        (location, (objc_object*)newObj);
}
Copy the code

The difference from objc_initWeak is DontCrashIfDeallocating. If newObj’s ISA has been marked deallocating or the class newObj belongs to does not support weak references, the __weak variable points to nil. No crash occurs.

Weak variable is set to nil

When an object is released and destroyed all its weak references are set to nil. It’s something we’ve heard a million times, but where’s the entrance? Since the object is destroyed, the entry should be in the object’s dealloc function.

The dealloc function is executed when the object reference count is 0. We can see the destruction process in dealloc: dealloc->_objc_rootDealloc->rootDealloc->object_dispose->objc_destructInstance->clearDeallocating->clearDeallocating_sl Ow, below we follow the source code to see the function of this way.

dealloc

Dealloc function:

// Replaced by CF (throws an NSException)
+ (void)dealloc {
    // Class objects cannot be destroyed, so their dealloc is empty inside.
}

// Replaced by NSZombies
- (void)dealloc {
    // Call the _objc_rootDealloc function directly.
    _objc_rootDealloc(self);
}
Copy the code

_objc_rootDealloc

_objc_rootDealloc function:

void
_objc_rootDealloc(id obj)
{
    ASSERT(obj);
    // Call rootDealloc directly.
    obj->rootDealloc(a); }Copy the code

rootDealloc

Struct objc_object rootDealloc

inline void
objc_object::rootDealloc(a)
{
    if (isTaggedPointer()) return;  
    // fixme necessary? Is it necessary? Object destructor on Tagged Pointer does not go through this process.

    // Call the free function directly to free the object
    
    Isa isa non-pointer type, i.e. the optimized ias_t type, which contains more information than the address of the class object
    // 2. No weak references
    // 3. There is no associated object
    // 4. No custom C++ destructor
    // 5. There are no reference counts in SideTable, that is, reference counts are all in extra_rc
    
    // After the above conditions are met, the object can be quickly released
    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

RootDealloc function

  1. judgeobjectIs TaggedPointer or not, if so, returns without any destructor. TaggedPointer does not go through this destructor process. It is not that the memory of TaggedPointer is not freed. The memory of TaggedPointer is freed by the system. Our ordinary object variables are in the heap and need to go through the release process.
  2. Next determine if the object can be freed quickly (free(this)CFunction frees memory. First determine whether the object is optimizedisaCounting mode (isa.nonpointer). If it is followed by the next judgment: the object does not existweakReference (! isa.weakly_referenced), there is no associated object (! isa.has_assoc), there is no customC++Destruction method (! isa.has_cxx_dtor), noSideTablerefcntsStore reference counts (! isa.has_sidetable_rc).
  3. In other cases, enterobject_dispose((id)this)Branch for slow release.

object_dispose

Object_dispose ((id)this) is called as object_dispose(id). Objc_destructInstance (obj) is called to destructinstance (obj), and free(obj) is used to free memory:

id 
object_dispose(id obj) {
    if(! obj)return nil;
    
    // It can be understood as the cleaning work before free
    objc_destructInstance(obj);
    
    // Free is free
    free(obj);
    
    return nil;
}
Copy the code

objc_destructInstance

Destructinstance Destroys an instance without freeing memory. Calls C++ destructors. Calls ARC ivar cleanup. Removes associative references. Returns obj. Does nothing if obj is nil.

Destroying the instance instead of freeing memory, which is the free function below. Call the C++ destructor, call ARC ivar cleanup, remove associated references, and return obj. If obj is nil, nothing is done.

void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        // Read all flags at once to improve performance.
        bool cxx = obj->hasCxxDtor(a);bool assoc = obj->hasAssociatedObjects(a);// This order is important.
        // This order is important.
        
        // C++ destructor
        if (cxx) object_cxxDestruct(obj);
        // Remove all associated objects and remove themselves from the Association Manager map
        if (assoc) _object_remove_assocations(obj);
        
        // So far you haven't seen the weak reference to the object being set to nil, it should be in the clearDeallocating function below, moving down
        obj->clearDeallocating(a); }return obj;
}
Copy the code

clearDeallocating

inline void 
objc_object::clearDeallocating(a)
{
    if (slowpath(! isa.nonpointer)) {// Slow path for raw pointer isa.
        // object isa isa primitive pointer type
        sidetable_clearDeallocating(a); }else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
        // Slow path for non-pointer isa with weak refs and/or side table data.
        // The isa of the object is optimized for isa_t
        clearDeallocating_slow(a); }assert(!sidetable_present());
}
Copy the code

clearDeallocating_slow

Slow path of clearDeallocating() for objects with Nonpointer Isa that were ever weakly referenced or whose retain count ever overflowed to the side table.

The slow path of the clearDeallocating() function, used for an object that had a weak reference or reservation count that spilled into the SideTable and had a non-pointer ISA. Weakly_referenced field of isa is set to true when a weak reference to an object is created. Weakly_referenced field of isa is set to true when a weak reference to an object is created Weakly_referenced is never set to false again, even if the object does not have any weak references in the future, which may be for performance reasons. Weak_entry_t is removed from the hash array of weak_table_t when all weak references of objects that have weak references do not exist.) . (and here the isa weakly_referenced | | isa. Has_sidetable_rc put together, because at the same time also need the object from the SideTable – > refcnts hash array removed.)

NEVER_INLINE void
objc_object::clearDeallocating_slow(a)
{
    ASSERT(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));

    // In the global SideTables, use this pointer as the key to find the corresponding SideTable
    SideTable& table = SideTables(to)this];
    
    / / lock
    table.lock(a);// If obj is weakly referenced
    if (isa.weakly_referenced) {
        // Clean this in weak_table of SideTable
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    
    // If the reference count overflows to SideTable->refcnts for preservation
    if (isa.has_sidetable_rc) {
        // Remove this from the reference-counting hash of SideTable
        table.refcnts.erase(this);
    }
    
    / / unlock
    table.unlock(a); }#endif
Copy the code

weak_clear_no_lock

Called by dealloc. nils out all weak pointers that point to the provided object so that they can no longer be used.

Called by dealloc to clear all weak references to supplied objects so that they are no longer used.

Weak_clear_no_lock is called here to clean up weak_table and set all weak references of this object to nil.

  void 
  weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
  {
      // Referent Specifies the object to be destroyed
      objc_object *referent = (objc_object *)referent_id;

      // Find the referent in weak_entry_t in weak_table hash array
      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
      // Temporary variable, record the starting address of weak_entry_t hash array, (or fixed-length weak_entry_t hash array start address)
      weak_referrer_t *referrers;
      size_t count;
      
      // Find the weak_referrer_t array and the length of the array
      if (entry->out_of_line()) {
          // Hash the starting address of the array
          referrers = entry->referrers;
          // The length is mask + 1
          count = TABLE_SIZE(entry);
      } 
      else {
          // Start address of an internal fixed-length array
          referrers = entry->inline_referrers;
          // The length is WEAK_INLINE_COUNT
          count = WEAK_INLINE_COUNT;
      }
      
      // Traversing the hash array of weak_entry_t points weak references to nil
      for (size_t i = 0; i < count; ++i) {
          // Get the address of the weak reference in the hash array (objc_object **)
          objc_object **referrer = referrers[i];
          
          if (referrer) {
              if (*referrer == referent) {
                  // If the weak reference variable refers to the referent, set its reference to nil
                  *referrer = nil;
              }
              else if (*referrer) {
                  / / error:
                  // If the stored weak reference does not point to the referent, this may be due to a logic error in the Runtime code
                  _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(a); }}}// Since the referent is being freed, the referent's weak_entry_t should also be removed from the weak_table hash array. Ensure hash table performance and lookup efficiency.
      weak_entry_remove(weak_table, entry);
  }
Copy the code

conclusion

When the weak reference of an object is created for the first time, a weak_entry_t is created with the pointer of the object and the address of the weak reference, and placed in the weak_table_t of SideTable where the object is located. Then the addresses of all weak references pointing to this object will be saved in the hash array of weak_entry_t of this object. When the object is destructed, the addresses of weak references saved in Weak_entry_t will be traversed and weak references will point to nil. Finally, weak_entry_t is removed from weak_table.

Refer to the link

Reference link :🔗

  • Objective-c Runtime mechanism (6) — The underlying implementation of weak references
  • The underlying iOS — weak describes the principle of object storage
  • SideTables, SideTable, Weak_table, weak_entry_t