一、循环引用的产生
-- :表示弱引用。
-> :表示强引用。
循环引用可以简单理解为对象A引用了对象B,而对象B又引用了对象A:A -> B -> A,此时双方都同时保持对方的一个引用,导致任何时候双方的引用计数都不为0,双方始终无法释放就造成内存泄漏。
当然不只是两个对象之间相互引用会形成循环引用,多个对象之间相互引用最终形成环同样会形成循环引用。
例如:A -> B -> C -> A。
二、容易造成循环引用的三个场景
1、delegate
self.tableView.delegate = self;
如果 delegate使用strong修饰就会构成循环引用:self -> tableView -> delegate -> self。
所以在定义delegate属性时使用weak便能解决这一问题:self -> tableView -- delegate -> self。tableView和delegate之间不是强引用,所以构不成循环。
规避delegate循环引用的杀手锏也是简单到哭:定义delegate属性时请用assign(MRC)或者weak(ARC),千万别手贱玩一下retain或者strong。
2、NSTimer
首先我们创建一个OneViewController,我们只看一下OneViewController什么时候销毁:
@interface OneViewController ()
@end
@implementation OneViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)dealloc {
NSLog(@"dealloc OneViewController");
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"从OneViewController返回!");
[self.navigationController popViewControllerAnimated:YES];
}
我们把OneViewController push出来,再pop出去,试验结果:
2019-02-18 23:46:45.621 --------[11119:550693] 从OneViewController返回!
2019-02-18 23:46:46.128 --------[11119:550693] dealloc OneViewController
由此可知,OneViewController一pop出去立即就销毁了,OneViewController的生命周期没有问题。
此时我们添加NSTimer:
@property(nonatomic, strong) NSTimer *time;
self.time = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(selector) userInfo:nil repeats:YES];
- (void)selector {
NSLog(@"self.time出来了!");
}
我们再次进行相同的操作,看一下结果:
2019-02-19 00:00:34.431 --------[11223:557177] self.time出来了!
2019-02-19 00:00:35.273 --------[11223:557177] 从OneViewController返回!
2019-02-19 00:00:35.430 --------[11223:557177] self.time出来了!
2019-02-19 00:00:36.431 --------[11223:557177] self.time出来了!
可以看出,pop OneViewController之后OneViewController并没有被dealloc,并且NSTimer还在运行。
NSTimer与OneViewController形成了循环引用:self -> time -> self,所以NSTimer与OneViewController都无法被销毁。
既然target:self不行,那么我们试一下weakSelf:
__weak typeof (self) weakSelf = self;
self.time = [NSTimer scheduledTimerWithTimeInterval:1 target:weakSelf selector:@selector(selector) userInfo:nil repeats:YES];
我们再次进行相同的操作,看一下结果:
2019-02-19 00:34:55.365 定时器和视图移动[11275:564482] self.time出来了!
2019-02-19 00:34:55.482 定时器和视图移动[11275:564482] 从OneViewController返回!
2019-02-19 00:34:56.365 定时器和视图移动[11275:564482] self.time出来了!
2019-02-19 00:34:57.366 定时器和视图移动[11275:564482] self.time出来了!
从结果来看,使用weakSelf对NSTimer而言同样会形成循环引用:self -> time -> weakSelf -> self。
最简单的解决方法为:当OneViewController不再显示的时候将NSTimer的清除即可,但NSTimer的清除可以放在很多地方进行,并且非常容易忘记,非是优解。
[self.time invalidate];
self.time = nil;
其他方法之后再补充。
3、block
我们看到,在block内部使用了self,xcode给出了warning。
网上大部分博客都表述为"block里使用了self导致循环引用",但事实真的是如此吗?其实这种说法是不严谨的,不一定要显式地出现"self"字眼才会引起循环引用。不通过self去访问变量,而是直接访问实例变量,如下:
即使在block内部没有显式地出现"self",只要你在block里用到了self所拥有的东西,一样会出现循环引用!
解决block循环引用的方法是使用__weak修饰self,然后在block里使用被修饰后的weakSelf来代替self:
__weak __typeof(self) weakSelf = self;
[self.reachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
NSLog(@"%@",weakSelf.reachabilityManager);
}];
但是仅仅使用__weak修饰self存在一个缺陷:__weak可能会导致内存提前回收weakSelf,在未执行NSLog()时,weakSelf就已经被释放了,然后执行NSLog()时就打印(null)。
所以为了解决这个缺陷,我们需要这样在block内部再用__strong去修饰weakSelf:
__weak __typeof(self) weakSelf = self;
[self.reachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
__strong __typeof(weakSelf) strongSelf = weakSelf;
NSLog(@"%@",strongSelf.reachabilityManager);
}];
我们发现上述这个方法确实解决所有问题,但是可能会有两个不理解的点:
- 即使用weakSelf又使用strongSelf,这么做和直接用self有什么区别?为什么不会有循环引用?这是因为block外部的weakSelf是为了打破环,从而使得没有循环引用,而block内部的strongSelf是为了防止weakSelf被提前释放,strongSelf仅仅是block的局部变量,存在栈中,在block执行结束后被回收,不会再造成循环引用。
- 这么做和使用weakSelf有什么区别?唯一的区别就是多了一个strongSelf,而这里的strongSelf会使self的引用计数+1,使得self只有在block执行完,局部的strongSelf被回收后,self才会dealloc。
使用最终方法基本可以解决百分之九十九的block的循环引用问题,但还是希望不要盲目遇到block中有self就直接下面这两行代码上去,最好能够理解地运用,根据实际情况去运用。
__weak __typeof(self) weakSelf = self
__strong __typeof(weakSelf) strongSelf = weakSelf
我们最后把转换强、弱引用的代码封装为宏定义,方便使用与阅读。
//弱引用
#define WeakSelf(weakSelf) __weak __typeof(self) weakSelf = self
//强引用
#define StrongSelf(strongSelf) __strong __typeof(weakSelf) strongSelf = weakSelf
当然这样使用了宏后,还是有一些缺陷:
- block内部必须使用strongSelf或weakSelf,很麻烦,不如直接使用self简便。
- 很容易在block内部不小心使用self,这样还是会引起循环引用,这种错误很难察觉。