进一步谈谈 __strong 和 __weak

2016-01-02 iOS

我是前言

关于在block 用__weak__strong来打破循环引用,你真的了解足够多么?做一题就知道。

来个题目

这是一段写在 ViewController 里的一个方法,仔细阅读下面这段代码,并回答问题。

- (void)foo {
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",strongSelf);
        });
    };
    self.block();
    [self.navigationController popViewControllerAnimated:YES];
}

问题一:调用 [self foo] 会发生上面?如果把 NSLog(@"%@",strongSelf); 中的 strongSelf 替换成 weakSelf 呢?


问题二:我们知道对象,声明一个对象,如果不显示指定修饰符,默认会加上修饰符__strong,那上面这段代码里为什么还要显式地写上: __strong typeof...?如果不写会怎么样?


问题三:用__strong 去修饰一个普通对象会增加该对象的引用计数,那么如果用__strong去修饰一个被__weak修饰的变量,会怎么样?


我是解析

用不用__strongSelf的区别

先说说把 strongSelf 替换成 weakSelf 会怎么样,由于 self.block(); 这行代码执行完毕后,马上执行 [self.navigationController popViewControllerAnimated:YES]; 此时 ViewController 销毁,因为 self.block 里边 weakSelf 已经变成 nil ,所以 NSLog 打印出空值(null),而用 __strong 就可以打印得到self的对象信息(这里是一个ViewController),接着 ViewController 销毁。

现象分析

我们先来看看,写上 __strong typeof(weakSelf) strongSelf = weakSelf; 的情况:
勾选 Xcode 菜单栏上的 Debug - Workflow - Always Show DisAssembly,在这句代码打一个断点,,然后 Run 看看情况:


从反汇编可以可以看到,这句代码执行时,会调用 objc_loadWeakRetained ,继续跟踪 objc_loadWeakRetained ,会发现在这个函数后面又调用了 retainWeakReference,最终取出被 __weak 修饰变量所引用的对象并进行一次 retain,造成计用计数 +1

如果去掉 __strong,直接写:typeof(weakSelf) anotherObj = weakSelf,会是怎么样?


通过前面一样的调试方法可以看到去掉代码执行时,调用了 objc_copyWeak ,该函数会先调用 objc_loadWeakRetained 取得对象(同时也导致导致引用计数+1), 然后调用 objc_initWeak ,将 anotherObj这个变量指向取得的对象(就上面被__weak修饰那个对象),最终调用一次 release,引用计数 -1一前一后相互抵消,最终引用计数没变

所以被 __strong 修饰的 strongSelf,会增加 self 的引用计数增加,从而防止self对象过早释放,这里又引发另一个疑问,strongSelf 是在Block大括号里边定义的,按理说 strongSelf 一旦超出这个大括号应该被释放掉,从而导致 self 被释放,显然,在 [self.navigationController popViewControllerAnimated:YES] 这就代码执行之时,已经过了大括号,为什么 strongSelf 仍然没有被释放?

请注意,dispatch_after 函数的第三个参数类型 dispatch_block_t,它也是一个 block类型,我们知道对一个在栈上的 block 调用 -copy 方法时会对 block内部的对象做一次 retain 操作(如果你不清楚这个机制,可以看看这篇文章),显然 dispatch_after 复制并持有了这个上 block

^{ //一开始在该 block 在栈上
    NSLog(@"%@",strongSelf);
});

此时该 block 被复制到,这个过程中 block 里的 strongSelf 会被堆上的 block 持有,最终self 又被 retain 了一次,所以在 strongSelf 过了大括号超出作用域而 release 后,在 dispatch_after 里边的代码没有被执行之前,self 的引用计数仍不为0,而当 dispatch_after 里边的代码执行过后,会自动销毁这个block,里边引用的对象自然会做一次 release 从而可以释放对象。
到了这里,想想如果 dispatch_after block 里边是 weakSelf 会怎么样? 通过上面的结论,可以简单理解成在 block 复制时,并不会对retain weak对象,在 block 执行之前,weak对象一旦被释放,就会出现nil情况。

我是结论

1.一个用 __weak 修饰过的变量 a,如果再使用 __Strong 修饰的变量 b 指向它, a指向的对象 的引用计数会+1
2.当 block 从栈复制到堆上时,weak属性的变量所指向对象不会增加引用计数

References

《Pro multithreading and memory management for iOS and OS X》

Comments
Write a Comment