Under ARC, we want to obtain the object referred to by objc_loadWeakrelease and objc_loadWeak, and under MRC, we want to obtain objc_loadWeak (although the address of the object is in the weak variable). There is a difference in understanding here from going directly to the object memory to read the contents through the pointer.

Looking at the function implementation above, you can see that as long as an object is marked as deallocating, even though the weak reference to the object is still pointing to object memory and the object is not fully freed, accessing the object through its weak reference will result in nil.

The sample code

M: Debug -> Debug Workflow ->alway show Disassembly. Let’s verify which functions are called during the execution of the function:

#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        id obj = [NSObject new];
        __weak id weakPtr = obj;
        
        printf("Start tag");
        {
            NSLog(@"% @", weakPtr);
        }
        printf("End tag"); // ⬅️ Interrupt here
    }
    return 0;
}
Copy the code

Analysis assembly code

We’ll just focus on the code between the Start tag and the End tag here. Running the code we can capture the following:

The ARC under:

.0x100000c75 <+69>:  movb   $0x0, %al
    0x100000c77 <+71>:  callq  0x100000e24               ; symbol stub for: printf
    0x100000c7c <+76>:  leaq   -0x18(%rbp), %rdi
    0x100000c80 <+80>:  callq  0x100000e0c               ; symbol stub for: objc_loadWeaktr ⬅️ Reads weakPtr. Callq calls the objc_loadWeaktr function0x100000c85 <+85>:  movq   %rax, %rbx
    0x100000c88 <+88>:  leaq   0x391(%rip), %rdi         ; @"% @"
    0x100000c8f <+95>:  movq   %rbx, %rsi
    0x100000c92 <+98>:  xorl   %eax, %eax
    0x100000c94 <+100>: callq  0x100000ddc               ; symbol stub for: NSLog
    0x100000c99 <+105>: jmp    0x100000c9b               ; <+107> at main.m:20:13
    0x100000c9b <+107>: movq   %rbx, %rdi
    0x100000c9e <+110>: callq  *0x36c(%rip)              ; (void *)0x00007fff6cb04d20: objc_release ⬅️ : objc_release is called when I see the right curly bracket and I cancel out with retain when I read...Copy the code

MRC: (Compile Sources, set Compiler Flags for main.m to -fno-objc-ARC)

.0x100000cbe <+78>:  callq  0x100000e1c               ; symbol stub for: objc_loadWeak ⬅️ Reads weakPtr. The callq directive calls objc_loadWeak0x100000cc3 <+83>:  leaq   0x34e(%rip), %rdi         ; @"% @"
0x100000cca <+90>:  movq   %rax, %rsi
0x100000ccd <+93>:  xorl   %eax, %eax
0x100000ccf <+95>:  callq  0x100000dec               ; symbol stub for: NSLog
0x100000cd4 <+100>: jmp    0x100000cd6               ; <+102> at main.m:22:9.Copy the code

Gets the weakly referenced object

From the above assembly code, we can make the following summary and throw out some conclusions, which we will prove step by step:

  1. In ARC mode, when you obtain weak, you will call objc_loadWeakvariable and then call objc_release once when you want to leave the current scope. Retained object::rootRetain () ¶ Objc_object ::rootRetain () will be used across weak points in objc_loadWeakthread, but will increase the reference count by one. The objc_release function (internally: objc_object::release) is called just before the object is out of scope to subtracted the reference count of the object by one. This increment and subtraction is to ensure that the object is not destroyed when it is read through the weak variable, which does not have a strong reference to the object.

  2. In MRC mode, objc_loadWeak is called when a weak pointer is obtained. The internal implementation is: Objc_autorelease (objc_loadWeakinformation (location)), that is, objc_autorelease is used to offset the increment of reference count during weak variable reading and ensure that the object can be released properly.

objc_loadWeakRetained

The objc_loadWeakRetained function source code can be verified by verifying the location parameter. Again, let’s use objc_loadWeaklocation for the breakpoint, and use LLDB to display the following:

// The right part of the console data:
// obj Object address
obj    NSObject *    0x10112f010    0x000000010112f010
// weakPtr points to the same object address as OBj
weakPtr    NSObject *    0x10112f010    0x000000010112f010
Copy the code
// Right side of the console:
(lldb) p weakPtr // Print weakPtr pointer
(NSObject *) $0 = 0x000000010112f010

// objc_loadWeakRetained function parameter location
(lldb) p location
(id *) $1 = 0x00007ffeefbff558

// Print the contents of the location memory space, which is our obj object
(lldb) p *location
(NSObject *) $2 = 0x000000010112f010

// Check the register
(lldb) register read
General Purpose Registers:
       rax = 0x000000010112f010
       rbx = 0x00007ffeefbff558
       rcx = 0x0000000000000307
       rdx = 0x00007ffeefbff398
       
       rdi = 0x00007ffeefbff558 // rdi put the location parameter
       
       rsi = 0x0000000000000000
       rbp = 0x00007ffeefbff520
       rsp = 0x00007ffeefbff3f0
        r8 = 0x0000000000000001
        r9 = 0x0000000000000002
       r10 = 0x00007ffeefbff6e8
       r11 = 0x00000001003d3af0  libobjc.A.dylib`::objc_loadWeakRetained(id *) at NSObject.mm:464
       r12 = 0x0000000000000000
       r13 = 0x0000000000000000
       r14 = 0x0000000000000001
       r15 = 0x0000000000000000
       rip = 0x00000001003d3b02  libobjc.A.dylib`::objc_loadWeakRetained(id *) + 18 at NSObject.mm:473:12
    rflags = 0x0000000000000206
        cs = 0x000000000000002b
        fs = 0x0000000000000000
        gs = 0x0000000000000000

(lldb) memory read 0x00007ffeefbff558 // Read the contents of 0x00007FFeefBFF558
0x7ffeefbff558: 10 f0 12 01 01 00 00 00 00 00 00 00 00 00 00 00.0x7ffeefbff568: 00 00 00 00 00 00 00 00 80 f5 bf ef fe 7f 00 00. (lldb)// Read 0x7ffeefBFF558 backwards
0x010112f010 // This is our obj object
Copy the code
/* Once upon a time we eagerly cleared *location if we saw the object was deallocating. This confuses code like NSPointerFunctions which tries to pre-flight the raw storage and assumes if the storage is zero then the weak system is done interfering. That is false: the weak system is still going to check and clear the storage later. This can cause objc_weak_error complaints and So we now don't touch the storage until deallocation completes. ⚠️⚠️⚠️ as long as deallocation is marked, regardless of whether the object is released, If the weak variable is set to nil, if you try to get an object by weak you get nil. * /

id
objc_loadWeakRetained(id *location)
{
    // * The object to which the location points
    id obj;
    id result;
    Class cls;

    SideTable *table;
    
 retry:
    // fixme std::atomic this load
    // Retrieve the object address stored in location
    obj = *location;
    
    // Return nil if the object does not exist
    if(! obj)return nil;
    
    // Return obj if the object is tagged Pointer
    if (obj->isTaggedPointer()) return obj;
    
    // Obtain the SideTable in which the object resides from global SideTables
    table = &SideTables()[obj];
    
    / / lock
    table->lock(a);if(*location ! = obj) {// If *location was modified by another thread, unlock and re-execute from retry
        table->unlock(a);goto retry;
    }
    
    result = obj;

    // Retrieve the class to which obj belongs
    cls = obj->ISA(a);// In objc-runtimenew. h __LP64__ environment:
    // class or superclass has default retain/release/autorelease/retainCount/
    // _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference
    // #define FAST_HAS_DEFAULT_RR (1UL<<2)
    
    // hasCustomRR represents an override of CLS:
    // 1. retain/release/autorelease
    // 2. retainCount/_tryRetain
    // 3. _isDeallocating
    // 4. retainWeakReference/allowsWeakReference
    // This method is not overridden, so the value is usually false, and the inverse is true
    
    if (! cls->hasCustomRR()) {
    
        // Fast case. We know +initialize is complete because
        // default-RR can never be set before then.
        // We know that + initialize is done because default-RR can never be set until then.
        
        // The assertion is performed if it is not initialized
        ASSERT(cls->isInitialized());
        
        // Try to Retain obj
        // rootTryRetain internal implementation: return rootRetain(true, false)? true : false;
        
        // If obj is destructing deallocating, that is, if obj's ISA_t bit field: uintptr_T deallocating: 1;
        Obj ->rootTryRetain() returns false, if result = nil, then we can only get nil when we read the object that weak points to.
        // Otherwise obj->rootTryRetain() returns true, obj does a normal retain and returns result at the end of the function.
        
        if (! obj->rootTryRetain()) { result = nil; }}else {
        // Slow case. We must check for +initialize and call it outside
        // the lock if necessary in order to avoid deadlocks.
        // We must check + initialize and call it if necessary after lock has been unlocked to avoid deadlock.
        
        // Ensure that the CLS is initialized
        if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) {
            // Determine whether CLS implements retainWeakReference function. If not, return nil
            BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL))
                class_getMethodImplementation(cls, @selector(retainWeakReference));
            if ((IMP)tryRetain == _objc_msgForward) {
                result = nil;
            }
            RetainWeakReference is implemented in nsobject. mm by default return [self _tryRetain]
            else if (! (*tryRetain)(obj, @selector(retainWeakReference))) {
                // If the retainWeakReference function returns false, return nilresult = nil; }}else {
            // Unlock, initialize, and retry from the Retry location
            table->unlock(a);class_initialize(cls, obj);
            gotoretry; }}/ / unlock
    table->unlock(a);/ / return the result
    return result;
}
Copy the code

The function of objc_loadWeakanchorage can be interpreted as follows: In ARC, the compiler inserts an objc_release function after the object, and in MRC, it puts the returned object into the automatic release pool. Both methods ensure that the object is released properly.

In ARC, there are three pairs of objc_loadWeakrelease and objc_loadWeakrelease that match each other.

#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        id obj = [NSObject new];
        
        printf("Start tag");
        {
            __weak id weakPtr = obj;
            
            NSLog(@"% @", weakPtr);
            NSLog(@"% @", weakPtr);
            NSLog(@"% @", weakPtr);
        }
        printf("End tag"); // ⬅️ Interrupt here
    }
    return 0;
}
Copy the code
    0x100000d21 <+49>:  leaq   0x228(%rip), %rdi         ; "Start tag".0x100000d42 <+82>:  callq  0x100000e9c               ; symbol stub for: objc_loadWeakRetained / / 1 ⃣ ️.0x100000d56 <+102>: callq  0x100000e78               ; symbol stub for: NSLog
    ...
    0x100000d5d <+109>: movq   0x2a4(%rip), %rax         ; (void *)0x00007fff71cb7d20: objc_release / / 1 ⃣ ️.0x100000d6d <+125>: callq  0x100000e9c               ; symbol stub for: objc_loadWeakRetained / / 2 ⃣ ️.0x100000d81 <+145>: callq  0x100000e78               ; symbol stub for: NSLog
    ...
    0x100000d88 <+152>: movq   0x279(%rip), %rax         ; (void *)0x00007fff71cb7d20: objc_release / / 2 ⃣ ️.0x100000d98 <+168>: callq  0x100000e9c               ; symbol stub for: objc_loadWeakRetained / / 3 ⃣ ️.0x100000dac <+188>: callq  0x100000e78               ; symbol stub for: NSLog
    ...
    0x100000db6 <+198>: callq  *0x24c(%rip)              ; (void *)0x00007fff71cb7d20: objc_release / / 3 ⃣ ️
    0x100000dbc <+204>: leaq   -0x20(%rbp), %rdi
    0x100000dc0 <+208>: callq  0x100000e90               ; symbol stub for: objc_destroyWeak
->  0x100000dc5 <+213>: leaq   0x18e(%rip), %rdi         ; "End tag".Copy the code

Objc_loadWeak source code:

/** * This loads the object referenced by a weak pointer and returns it, * after retaining and autoreleasing the object to ensure that it stays * alive long enough for the caller to use it. * This function would be used anywhere a __weak variable is used in an expression. * This function would load objects referenced by weak Pointers, And * returns it after retaining and automatically releasing the object to ensure that it remains alive long enough for the caller to use. This function can be used anywhere in an expression where the __weak variable is used. * * @param location The weak pointer address * * @return The object pointed to by \e location, or \c nil if \e location is \c nil. */
id
objc_loadWeak(id *location)
{
    if(! *location)return nil;
    return objc_autorelease(objc_loadWeakRetained(location));
}
Copy the code

objc_copyWeak

This function is called when one weak variable is assigned to another, as follows:

#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        id obj = [NSObject new];
        
        printf("Start tag");
        {
            __weak id weakPtr = obj;
            __weak id weakPtr2 = weakPtr;
        }
        printf("End tag");
    }
    return 0;
}
Copy the code

Assembly code:

.0x100000e52 <+82>:  callq  0x100000eda               ; symbol stub for: objc_copyWeak ⬅️ The callq directive calls objc_copyWeak...Copy the code

Objc_copyWeak ();

/** * This function copies a weak pointer from one location to another, * when the destination doesn't already contain a weak pointer. It * would be used for code like: * * __weak id src = ... ; * __weak id dst = src; * * This function IS NOT thread-safe with respect to concurrent * modifications to the destination variable. (Concurrent  weak clear is safe.) * * @param dst The destination variable. * @param src The source variable. */
void
objc_copyWeak(id *dst, id *src)
{
    // First get the object from SRC weak and increment the reference count by 1
    id obj = objc_loadWeakRetained(src);
    
    // Initialize the DST weak variable
    objc_initWeak(dst, obj);
    
    // obj the obj reference count is subtracted by 1, corresponding to +1 when reading, so that the object can be released normally
    objc_release(obj);
}
Copy the code

objc_moveWeak

objc_moveWeak

/** * Move a weak pointer from one location to another. * Move a weak pointer from one location to another. * Before the move, the destination must be uninitialized. * After the move, the source is nil. * After the move, the source is nil. * * This function IS NOT thread-safe with respect to concurrent * modifications to either weak variable. (Concurrent ) * This function is not thread safe. * /
void
objc_moveWeak(id *dst, id *src)
{
    // Copy SRC weak to DST weak
    objc_copyWeak(dst, src);
    
    / / SRC
    objc_destroyWeak(src);
    
    // Set SRC to nil
    *src = nil;
}
Copy the code

Writing here, I feel that the principle of weak learning can be completed temporarily to do a small end.

Refer to the link

Reference link :🔗

  • Explain how to get weak
  • ObjC Runtime implementation of Weak (1)
  • An in-depth look at thread safety and automatic setting of weak Pointers