preface

Weak underlying principle of this article # iOS weak underlying principle of implementation (a) and the following several articles have been very clear, you can follow a serious study.

In the fourth article # iOS Weak underlying implementation Principle (iv), when introducing objc_loadWeakRetained variable access in ARC and MRC, there was no Demo provided. Here I repost an introduction from another blog. There is a reference link at the end of the article.

__weak effect on reference counting

Run the following code:

@interface TestObj : NSObject
@property (nonatomic.copy) void(^block)(void);
- (void)testMethod;
@end
  
@implementation TestObj
- (void)dealloc {
    NSLog(@"obj dealloc");
}

- (void)testMethod {
    if (self.block) {
        self.block();
    }
    NSLog(@ "% @".self);
}
@end

/ / in the ViewController
- (void)triggerWeakAction00 {
    __block TestObj *testObj = [TestObj new];
    testObj.block = ^{
        testObj = nil;
    };

    [testObj testMethod];
}
- (void)triggerWeakAction01 {
    __block TestObj *testObj = [TestObj new];
    testObj.block = ^{
        testObj = nil;
    };
    __weak typeof(testObj) weakObj = testObj;
    [weakObj testMethod];
}
Copy the code

What do you think will happen when triggerWeakAction00 and triggerWeakAction01 are called? Please test it!

You will find that triggerWeakAction00 crashes, but triggerWeakAction01 does not. Why? What magical effect does __weak have?

TriggerWeakAction01 ();

0x105401e88 <+248>: movq   -0x38(%rbp), %rax
0x105401e8c <+252>: movq   0x28(%rax), %rsi
0x105401e90 <+256>: leaq   -0x80(%rbp), %rax
0x105401e94 <+260>: movq   %rax, %rdi
0x105401e97 <+263>: movq   %rax, -0x98(%rbp)
0x105401e9e <+270>: callq  0x10540268e               ; symbol stub for: objc_initWeak
0x105401ea3 <+275>: movq   -0x98(%rbp), %rdi
0x105401eaa <+282>: movq   %rax, -0xa0(%rbp)
0x105401eb1 <+289>: callq  0x105402694               ; symbol stub for: objc_loadWeakRetained
0x105401eb6 <+294>: movq   %rax, %rcx
0x105401eb9 <+297>: movq   0x31e0(%rip), %rsi        ; "testMethod"
0x105401ec0 <+304>: movq   0x2151(%rip), %rdx        ; (void *)0x00007fff20173780: objc_msgSend
0x105401ec7 <+311>: movq   %rax, %rdi
0x105401eca <+314>: movq   %rcx, -0xa8(%rbp)
0x105401ed1 <+321>: callq  *%rdx
0x105401ed3 <+323>: jmp    0x105401ed8               ; <+328> at ViewController.m
0x105401ed8 <+328>: movq   -0xa8(%rbp), %rax
0x105401edf <+335>: movq   %rax, %rdi
0x105401ee2 <+338>: callq  *0x2138(%rip)             ; (void *)0x00007fff2018f760: objc_release
0x105401ee8 <+344>: leaq   -0x80(%rbp), %rax
0x105401eec <+348>: movq   %rax, %rdi
0x105401eef <+351>: callq  0x105402682               ; symbol stub for: objc_destroyWeak
Copy the code

We can clearly see the life cycle of __weak:

  • Objc_initWeak (create)
  • objc_destroyWeak

But what is objc_loadWeakanchorage? So I looked at the source code for OBJ4-750 (the version is a little old, but it doesn’t matter) :

/* 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 crashes. So we now don't touch the storage until deallocation completes. */

id
objc_loadWeakRetained(id *location)
{
    id obj;
    id result;
    Class cls;

    SideTable *table;
    
 retry:
    // fixme std::atomic this load
    obj = *location;
    if(! obj)return nil;
    if (obj->isTaggedPointer()) return obj;
    
    table = &SideTables()[obj];
    
    table->lock();
    if(*location ! = obj) { table->unlock();goto retry;
    }
    
    result = obj;

    cls = obj->ISA();
    if (! cls->hasCustomRR()) {
        // Fast case. We know +initialize is complete because
        // default-RR can never be set before then.
        ASSERT(cls->isInitialized());
        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.
        if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) {
            BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL))
                class_getMethodImplementation(cls, @selector(retainWeakReference));
            if ((IMP)tryRetain == _objc_msgForward) {
                result = nil;
            }
            else if(! (*tryRetain)(obj, @selector(retainWeakReference))) { result = nil; }}else {
            table->unlock();
            class_initialize(cls, obj);
            goto retry;
        }
    }
        
    table->unlock();
    return result;
}
Copy the code

__weak retains the weakly referenced object before sending the message, and then performs objc_release on the object after using it.

I believe you have figured out why triggerWeakAction00 crashed while triggerWeakAction01 did not!

So we call [weakObj testMethod] many times; ? (Try it out for yourself and watch the compilation)

reference

Understand weak’s delayed release from a crash