一.NSTimer
1.创建NSTimer
@interface NSTimer : NSObject
// 创建一个定时器,但是没有添加到运行循环,需要在创建定时器后手动的调用 NSRunLoop 对象的 addTimer:forMode: 方法
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
// 创建一个定时器,但是没有添加到运行循环,需要在创建定时器后手动的调用 NSRunLoop 对象的 addTimer:forMode: 方法
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
// iOS 10 新添加的方法,timer的创建方式,可以解决与target的循环引用问题,我们需要在创建定时器后手动的调用 NSRunLoop 对象的 addTimer:forMode: 方法
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
// 创建一个定时器,并将定时器的添加到当前的runloop中,当前run loop中模式为默认模式
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
// iOS 10 新添加的方法,timer的创建方式,可以解决与target的循环引用问题,自动添加到当前的runloop中,当前run loop中模式为默认模式
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
// iOS 10 新添加的方法,timer的创建方式,可以设置第一次的启动时间,,需要在创建定时器后手动的调用 NSRunLoop 对象的 addTimer:forMode: 方法
- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
// iOS 10 新添加的方法,timer的创建方式,可以设置第一次的启动时间,,需要在创建定时器后手动的调用 NSRunLoop 对象的 addTimer:forMode: 方法
- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)ti target:(id)t selector:(SEL)s userInfo:(nullable id)ui repeats:(BOOL)rep NS_DESIGNATED_INITIALIZER;
2.NSTimer的循环引用问题
循环引用产生的原因:创建NSTimer对象之后必须要将其加入到runloop中,API设计的时候就设定runloop会强引用timer以及timer的target以及userInfo资源对象。API解释是我们应当在合适的地方调用 invalidate()的方法。这样就会使runloop移除对timer,target,userInfo的强引用。
Apple文档说明
(Once scheduled on a run loop, the timer fires at the specified interval until it is invalidated. A nonrepeating timer invalidates itself immediately after it fires. However, for a repeating timer, you must invalidate the timer object yourself by calling its
invalidate
method. Calling this method requests the removal of the timer from the current run loop; as a result, you should always call theinvalidate
method from the same thread on which the timer was installed. Invalidating the timer immediately disables it so that it no longer affects the run loop. The run loop then removes the timer (and the strong reference it had to the timer), either just before theinvalidate
method returns or at some later point. Once invalidated, timer objects cannot be reused.
一旦添加到run loop中,定时器会在指定的时间间隔开启直到失效。一个不重复运行的定时器在开启后会立即失效。但是,对于一个重复的定时器,你必须通过调用invalidate
方法使定时器失效。调用这个方法将定时器从当前run loop中移除。所以,你必须在安装定时器相同的线程中调用invalidate
方法。定时器失效后不在run loop中生效,run loop会移除定时器(和对定时器的强引用),无论在invalidate
方法之前返回或之后。一旦失效,定时器不能再使用
)
解决内存泄漏方案
@interface TimerViewController ()
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation TimerViewController
- (void)viewDidLoad {
[super viewDidLoad];
//weakSelf,timer并没有强引用TimerViewController
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
[weakSelf timerRun];
}];
}
- (void)timerRun {
NSLog(@"%s", __func__);
}
- (void)dealloc {
[self.timer invalidate];
NSLog(@"%s", __func__);
}
二.GCD
GCD中除了主要的Dispatch Queue外,还有较次要的Dispatch Source。GCD定时器是dispatch_source_t类型的变量,其可以实现更加精准的定时效果。
通过dispatch_source_t 创建计时器
//创建全局的并发队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//创建定时器
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//开始时间
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, 3.0 * NSEC_PER_SEC);
//间隔时间,2秒
uint64_t interval = 2.0 * NSEC_PER_SEC;
/** 设置定时器
* para2: 任务开始时间
* para3: 任务的间隔
* para4: 可接受的误差时间,设置0即不允许出现误差
* Tips: 单位均为纳秒
*/
dispatch_source_set_timer(self.timer, start, interval, 0);
/** 设置定时器任务
* 可以通过block方式
* 也可以通过C函数方式
*/
dispatch_source_set_event_handler(self.timer, ^{
NSLog(@"----self.timer---");
});
//启动timer
dispatch_resume(self.timer);