进一步谈谈 __strong 和 __weak
我是前言
关于在block 用__weak
和__strong
来打破循环引用,你真的了解足够多么?做一题就知道。
来个题目
- (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》