在iOS开发中,我们经常会需要做一个定时任务,可能很多的开发者第一个反应都是NSTimer了,实际上NSTimer在使用过程中经常会遇到一些问题的,其实我们还可以使用GCD_Source的方式来创建一个定时任务
1.将timer添加到当前timer执行的线程RunLoop中
NSTimer *timer = [NSTimer timerWithTimeInterval:5.0 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
当我们将一个定时器添加到RunLoop后,这个定时器就能够定时启动这个任务了,实际上我们还可以用
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
这种方式创建的timer就等效的等于上面的两句代码,实际上这样的一个定时器是有些问题的,
第一点,当我们在点击屏幕,或者滑动屏幕时,我们主线程的RunLoop就会进入UITrackingRunLoopMode这个mode下,那么我们创建的定时器是在NSDefaultRunLoopMode模式下进行的,所有我们再滑动屏幕时定时器是没有办法工作的,我们在将定时器添加到RunLoop中时,可以指定它的mode,只需要将NSDefaultRunLoopMode改为NSRunLoopCommonModes即可
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
这个模式下定时器,即可在NSDefaultRunLoopMode,也可在UITrackingRunLoopMode两种模式下工作了,其实这样做已经可以基本解决大部分的需求了,这样的定时器仍然还有一个问题
第二点,我们的定时器是被添加在RunLoop中的,当我们的RunLoop进行一次循环处理任务时,是首先处理Source任务的,如果我们的Source任务是一个非常耗时的操作那么我们的Timer要执行的任务可能就会被RunLoop延后去执行,所以有时候我们的timer执行时间间隔经常是不准确的。为了达到一个精准度非常高的定时效果我们可以使用GCD的Source创建timer。
第三点,需要提醒大家的是,第一种方式创建的timer看似是没有什么问题的,实际上当我们将在创建timer时需要制定一个target,timer为了保证执行action会将target retain一次这样可能会影响到你的target释放,造成内存泄露,想要解决这个问题,我们还需要手动的释放掉该timer。
第四点,如果你的timer是在主线程创建的,可能并不会发现有什么问题,因为我的主线程的RunLoop默认是开启的;如果你在一个子线程里创建的timer,需要在这个子线程定时的执行一个方法是,你会发现无论你怎么添加到RunLoop中timer都不会执行,原因是因为我们的子线程的RunLoop默认是关闭的,当我们的子线程的任务执行完后该线程就会销毁,所以不会再执行timer,你可能会说我将这个线程强引用就好了,不让这个线程销毁不就可以了么?事实上仍然不会执行timer,因为这个线程执行完后,因为强引用了这个线程的原因这个线程确实不会被释放,但是这个线程会进入消亡状态,没有办法再次被唤醒,所有在子线程使用NSTimer我们还需要打开这个子线程的RunLoop
2.使用GCD来创建timer
dispatch_queue_t queue = dispatch_queue_create("timerQueue", DISPATCH_QUEUE_CONCURRENT);
// 创建一个定时器
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//设置定时器开启时间
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC);
// 定时器执行任务的间隔时间
uint64_t interval = 2 * NSEC_PER_SEC;
dispatch_source_set_timer(timer, start, interval, 0);
//设置定时器执行的任务
dispatch_source_set_event_handler(timer, ^{
NSLog(@"-----------");
});
// 定时器默认是关闭的,需要手动开启
dispatch_resume(timer);
通过GCD创建的timer可以通过控制台的打印看到间隔的时间,精准度相对于第一种方式要高的多,而且也不会有NSTimer的那几个问题,因为GCD的timer是Source是不受到RunLoop影响的。