关于 weak 的其它的一些内容

前言

关于 weak 的底层原理这篇文章 # iOS weak 底层实现原理(一) 以及后续的几篇文章已经讲的非常清楚了,大家可以跟着认真研究一番。

在第四篇文章 # iOS weak 底层实现原理(四):ARC 和 MRC 下 weak 变量的访问过程 中在介绍 objc_loadWeakRetained 时,并没给出对应的 Demo,我这里转发一个其它博客的介绍的,文末有参考链接。

__weak 对引用计数的影响

运行下面的代码:

@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

// 在 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];
}
复制代码

大家觉得 triggerWeakAction00triggerWeakAction01 调用之后会有什么问题?请测试一下!

你会发现 triggerWeakAction00 崩溃了,而 triggerWeakAction01 却没有,这是为什么?__weak 有什么神奇的作用呢?

下面是 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
复制代码

我们可以明显的看到 __weak 变量的生命周期:

  • objc_initWeak (创建)
  • objc_destroyWeak (销毁)

但是 objc_loadWeakRetained 是什么?于是查看了 obj4-750 的源码(版本有点老,但没关系):

/*
  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;
}
复制代码

__weak 在发送消息之前对弱引用的对象进行 retain 操作,使用完成后再对对象进行 objc_release 操作。

到此我相信你已经弄明白了 triggerWeakAction00 崩溃了,而 triggerWeakAction01 却没有的原因!

所以我们多次调用 [weakObj testMethod]; 呢?(可以自己尝试一下,并观察汇编)

参考

从一个crash理解weak的延迟释放

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享