在iOS中使用NSTimer(定时器)不当会引发内存泄漏.
NSTimer 有多种创建方式,具体可以看这位朋友的文章:https://blog.51cto.com/u_16099225/6716123
我这里主要讲使用NSTimer 会引发的内存泄漏情况以及解决方法:
内存泄漏出现的场景:
VC A push 到VC B, VC B里启动了一个 NSTimer, 然后VC B push 到VC C (或者 pop 回VC A), 此时 VC B里的 NSTimer 仍在执行(这是不对的),造成的原因就是 VC B里的 NSTimer 没有被及时释放(销毁)掉,我们称之为内存泄漏.
常见的有以下创建方式:
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor greenColor];
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerClick) userInfo:nil repeats:YES];
}
- (void)timerClick
{
NSLog(@"%s",__FUNCTION__);
}
- (void)dealloc
{
NSLog(@"走了 dealloc方法");
NSLog(@"%s",__FUNCTION__);
[self.timer invalidate];
}
不管是 :self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerClick) userInfo:nil repeats:YES];
还是:NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerClick) userInfo:nil repeats:YES];
这两种创建的方式 都有一个 target: self, 意思是timer 强引用了当前的self (VC), 那么创建的时候当前的self (VC)又强引用了 timer,你引用我,我引用你,这就造成了循环引用,所以VC B push (或者pop)的时候, arc内存管理会判断我们还需要这个内存(PS:arc内存管理的模式是,自动销毁我们不需要的内存). 就没有帮我们销毁.
所以我们发现 VC B 退出后没有走 - (void)dealloc 方法,timer也就没有被销毁.
解决方法:
一般解决self强引用的问题,会使用弱引用 weakSelf 来代替 self,但是这里不行,因为weakSelf 是用在block 中的.所以解决方法有两种:
方法一:
一是给NSTimer 和 VCB之间加一个“中间人”
原先是互相循环引用:
加个“中间人”
1.1.新建一个继承于NSObject 的类 MiddleObject ,在 MiddleObject 里用 weak 指针定义一个属性target,再构造一个初始化的类方法.
MiddleObject.h 文件:
MiddleObject.m 文件:
1.2.在VC B 中倒入 #import "MiddleObject.h".
将target:self 中的 self 替换成 中间类 MiddleObject
截图如下:
代码如下:
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor greenColor];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:[MiddleObject callWithTarget:self] selector:@selector(timerClick) userInfo:nil repeats:YES];
}
这样字就完成了
NSTimer 引用 MiddleObject,
MiddleObject 弱引用VC B
VC B 引用 NSTimer ,
三种关系,解决了两两循环引用的问题
方法二:
方法一有点麻烦,一般我们直接更换一种创建方法: 不使用 有 target的 方法:
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor greenColor];
__weak typeof(self) weakSelf = self;
//不使用target
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
[weakSelf timerClick];
}];
}
- (void)timerClick
{
NSLog(@"%s",__FUNCTION__);
}
- (void)dealloc
{
NSLog(@"走了 dealloc方法");
NSLog(@"%s",__FUNCTION__);
[self.timer invalidate];
}
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
[weakSelf timerClick];
}];
这种方法,没有target,本身不会造成循环引用, 且在block里也是有了 弱引用weakSelf开启定时器,所以 VC B 销毁的时候,timer 也会走- (void)dealloc方法,销毁掉timer .[self.timer invalidate];
方法三:自己手动销毁timer
虽然说现在都是arc自动销毁,但是我们也可以手动销毁,直接一点. 没有这么多华丽胡哨的.
创建方法还是有target的那种:
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerClick) userInfo:nil repeats:YES];
在VC B 的 - (void)viewWillDisappear:(BOOL)animated 方法里,直接手动销毁timer
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
NSLog(@"%s",__FUNCTION__);
[self.timer invalidate];
}