NSTimer
scheduledTimerWithTimeInterval与timerWithTimeInterval、initWithFireDate的区别
有三种方法来创建一个定时器
1.使用scheduledTimerWithTimeInterval
类方法创建计时器和进度上当前运行循环在默认模式(NSDefaultRunLoopMode)
2.使用timerWithTimerInterval
类方法创建计时器对象没有调度运行循环(RunLoop)
在创建它,必须手动添加计时器运行循环,通过调用adddTimer:forMode:方法相应的NSRunLoop对象
3.使用initWithFireDate
在创建它,必须手动添加计时器运行循环,通过使用addTimer:forMode:方法相应的NSRunLoop对象
- (void)execute {
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
}
- (void)execute {
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
//为什么在主线程不需要这句run,那是因为主线程有RunLoop而且已经启动
[[NSRunLoop currentRunLoop] run];
}
这两个代码效果是一样的,scheduledTimerWithTimeInterval相当于timerWithTimeInterval的两句。
使用过NSTimer的应该都清楚,NSTimer会和调用对象之间循环引用,从而导致内存泄漏。下面我们通过一个小测试,来说明这个问题。我们在一个VC的viewDidLoad方法里开启一个timer,在VC的dealloc方法里停止这个timer,如果没有循环引用,那么当我们退出这个VC之后,会调用VC的dealloc方法,从而停止timer,相关代码如下:
#import "TimerVC.h"
@interface TimerVC ()
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation TimerVC
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor grayColor];
[self startTimer];
}
- (void)startTimer {
if (!_timer) {
_timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(pollingTimer:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
}
[_timer fire];
}
- (void)stopTimer {
[self.timer invalidate];
self.timer = nil;
}
- (void)pollingTimer:(NSTimer *)timer {
NSLog(@"*****************TimerVC pollingTimer");
}
- (void)dealloc {
[self stopTimer];
}
@end
运行之后发现,即便退出了TimerVC,其内部的timer仍然没有停止,一直在后台打印log,而且不会调用TimerVC的dealloc方法,这是因为timer和VC相互引用,导致谁也不会被释放。为了解决这个问题,iOS 10中新增了两个API,允许我们使用block的方式执行定时代码,这两个API如下:
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;
我们把上面的startTimer方法改成如下方式:
- (void)startTimer {
if (!_timer) {
_timer = [NSTimer timerWithTimeInterval:2 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"*****************TimerVC pollingTimer");
}];
[[NSRunLoop mainRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
}
[_timer fire];
}
重新执行,发现关闭TimerVC之后,stopTimer方法得以执行,说明使用block方式确实能解决循环引用。但使用block的方式也会有两个至关重要的问题:
- 我们必须在调用者的dealloc方法里手动停止timer,否则timer会一直执行,并不被释放。
- 请找一个只支持ios 10以上的公司。。。
我们的项目中使用的是另外一种方式,代码在这里
@interface NSTimer (YYAdd)
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds block:(void (^)(NSTimer *timer))block repeats:(BOOL)repeats;
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)seconds block:(void (^)(NSTimer *timer))block repeats:(BOOL)repeats;
@end
//===============================================================================
@implementation NSTimer (YYAdd)
+ (void)_yy_ExecBlock:(NSTimer *)timer {
if ([timer userInfo]) {
void (^block)(NSTimer *timer) = (void (^)(NSTimer *timer))[timer userInfo];
block(timer);
}
}
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds block:(void (^)(NSTimer *timer))block repeats:(BOOL)repeats {
return [NSTimer scheduledTimerWithTimeInterval:seconds target:self selector:@selector(_yy_ExecBlock:) userInfo:[block copy] repeats:repeats];
}
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)seconds block:(void (^)(NSTimer *timer))block repeats:(BOOL)repeats {
return [NSTimer timerWithTimeInterval:seconds target:self selector:@selector(_yy_ExecBlock:) userInfo:[block copy] repeats:repeats];
}
@end
当我们在项目中比如一个VC里用上述代码初始化Timer时,各对象的引用关系如下:
截屏2021-05-14 下午8.42.29.png
这样,当退出VC时,LifeTracker由于没有持有者而被回收,从而走到了LiferTracker的dealloc方法里,我们在dealloc方法里调用了timer的invalidate方法,并将timer置为nil,这时,timerProxy也没有了持有者,所以也会被释放。整个过程没有内存泄漏,而且调用者不用手动调用timer的invalidate,方便快捷。
最终代码
#import "TimerVC.h"
#import "NSTimer+YYAdd.h"
@interface TimerVC ()
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation TimerVC
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor grayColor];
[self startTimer];
}
- (void)startTimer {
if (!_timer) {
__weak typeof(self) weakSelf = self;
_timer = [NSTimer scheduledTimerWithTimeInterval:2 block:^(NSTimer * _Nonnull timer) {
[weakSelf pollingTimer];
} repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
}
[_timer fire];
}
- (void)stopTimer {
[self.timer invalidate];
self.timer = nil;
}
- (void)pollingTimer {
NSLog(@"*****************TimerVC pollingTimer");
}
- (void)dealloc {
[self stopTimer];
NSLog(@"*****************================");
}
@end
网友评论