iOS-循环引用

一、循环引用的产生

-- :表示弱引用。

-> :表示强引用。

循环引用可以简单理解为对象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,这样还是会引起循环引用,这种错误很难察觉。

猜你喜欢

转载自blog.csdn.net/qq_36557133/article/details/87654407