NSTimer在iOS10之前只有如下两个常用方法
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
这两个方法都有一个target
参数,一般我们都把NSTimer对象作为一个属性,这个时候target
通常传递的是NSTimer属性所在的类对象,也就是Self
,由于NSTimer会强引用这个target
对象,所以导致出现一些问题。
销毁NSTimer计时器只能通过- (void)invalidate
方法,一般的需求下,我们期望计时器在视图的声明周期结束后停止计时器,所以我们可能想在视图类的- (void)dealloc
方法中调用[self.time invalidate]
来停止计时器。但是很遗憾,在视图消失的时候程序并不会调用- (void)dealloc
方法,所以计时器也一直在运行,造成了内存泄露。
例如:利用以下demo进行测试
#import <UIKit/UIKit.h>
@interface TestView : UIView
@end
#import "TestView.h"
@interface TestView ()
@property (strong, nonatomic) NSTimer *timer;
@end
@implementation TestView
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.timer = [NSTimer timerWithTimeInterval:5 target:self selector:@selector(timerTick) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
}
return self;
}
- (void)timerTick {
NSLog(@"*******");
}
- (void)dealloc {
if (self.timer) {
[self.timer invalidate];
self.timer = nil;
}
}
- (void)willMoveToSuperview:(UIView *)newSuperview {
[super willMoveToSuperview:newSuperview];
if (!newSuperview && self.timer) {
[self.timer invalidate];
self.timer = nil;
}
}
@end
我们知道iOS的内存管理是采用引用计数算法来实现的,当一个对象的引用计数为0的时候系统会回收该对象,对象的- (void)dealloc
方法要在对象的引用计数值归零之后才会被调用,由于NSTimer计时器一直强引用着视图类对象,所以视图类对象的引用关系里面一直有计时器对象,导致引用计数不能归零,所以- (void)dealloc
不会被调用。
至于解决办法,那就是在不需要计时器的时候调用- (void)invalidate
方法,但是如果我们计时器的开关是交由外部程序员去实现的,那么我们就只能在文档中嘱咐对方在不需要的时候一定要记得停止计时器,这并不保险。
但是例子中的定时器封装在一个类的类部要在viewcontroller销毁的时候怎么销毁定时器呢?
可以使用例子中的- (void)willMoveToSuperView:(UIView *)newSuperview
方法,可以在方法中销毁定时器,我们发现内存泄漏已经解决,dealloc方法可以执行了,此处dealloc方法可不写。