在之前的开发随笔小心NSTimer中的循环引用中介绍了NSTimer会因持有目标对象而引起内存泄漏(循环引用)以及解决办法.这篇随笔主要介绍iOS开发中其他几种实现定时任务的方式及其优缺点.
再谈NSTimer
要知道NSTimer工作方式,需要了解一下Runloop(这里只是给出描述,后续随笔会介绍).Runloop简单地说就是一个接收处理异步消息事件的循环,该循环中等待事件发生,然后将这个事件送到能处理它的地方。
或许你还不是很明白,没有关系,有个初步概念就不影响你理解NSTimer了.
NSTimer注册到Runloop中后,Runloop会周期性地触发注册的事件.但是这里的周期并不十分精确.Timer的属性Tolerance(容差)告诉系统可以允许的时间误差.一旦有大型的任务,错过这个周期需要执行的时间点±Tolerance,任务不会延后执行而是跳跃过去,下个周期继续.
NSObject的定时方法
这里不会介绍NSObject的所有方法,主要是介绍其应该注意的地方.在介绍之前补充一句:主线程会自动创建Runloop,子线程不会创建.
- performSelector:withObject:afterDelay:
如果该方法在主线程,可以正常执行,在子线程就会失效.这一系列方法有很多,如果读者感兴趣,可以阅读相关技术博客.这些方法的使用不是这篇随笔介绍的重点,因为这样博客有很多,再此重复意义不大.
CADisplayLink
A CADisplayLink object is a timer object that allows your application to synchronize its drawing to the refresh rate of the display.
Your application creates a new display link, providing a target object and a selector to be called when the screen is updated.
上面摘自苹果官方文档,CADisplayLink是一个定时器,使用它可以以和
屏幕刷新频率相同的频率将内容绘到屏幕上.创建一个该类对象,设置好target和selector,在屏幕更新时就会调用该selector.
其使用方法和NSTimer类似,这里不再赘述.下面介绍一个使用场合,如果读者需要用到,可以自行查阅相关资料,查阅资料无法解决时,也可以在评论一起探讨.
CADisplayLink默认使用屏幕固有的刷新频率,精确度比较高,使用场景也较为单一.一般用在需要不断重绘界面时,比如UI动画底层实现.在实际应用中,没有NSTimer使用的多.
如果对CADisplayLink感兴趣,可以点击阅读一个使用CADisplayLink开源库的源码.

类似Secret文字渐变效果的开源库
GCD定时
Runloop是用GCD的dispatch_source_t 实现的 Timer,GCD定时器不依赖RunLoop和mode,比NSTimer更加准时,性能更好.
使用GCD实现定时功能也很简单.
#import "ViewController.h"
@interface ViewController () {
dispatch_source_t _timer;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 获得一个queue
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// ①创建定时器
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
/**函数原型
dispatch_source_create(dispatch_source_type_t type,
uintptr_t handle,
unsigned long mask,
dispatch_queue_t queue);
*@param start
* 开始时间
*
* @param interval
*时间间隔
*
* @param leeway
* 容差(精确度)
*/
// ②设置定时开始时间 间隔 精确度
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
// ③设置定时执行任务
dispatch_source_set_event_handler(timer, ^{
static NSInteger cnt = 1;
NSLog(@"------------%ld", cnt++);
});
// ④激活定时器
dispatch_resume(timer);
// 持有定时器 避免ViewDidLoad方法结束timer变量消失引起定时器销毁
_timer = timer;
}
@end
GCD的使用很简单,不用担心记不住dispatch_source_t前几个字母时Xcode自动会提示你了.你要做的只是创建一个queue而已,其他代码都不用你写.

dispatch_source_t提示
几种实现定时任务的比较
| 方法来源 | 使用 |
|---|---|
| NSObject中的方法 | 适合延时执行任务,可以在子线程,也可回到主线程刷新UI.在子线程中延时时,必须自己创建Runloop |
| NSTimer | 一般延时和周期性任务都可以使用,在子线程中使用时,必须自己创建Runloop.使用比较简单,存在循环引用的风险,解决办法参考小心NSTimer中的循环引用 |
| CADisplayLink | 适合重复重绘界面,其频率和屏幕刷新固有频率相同 |
| GCD中的方法 | 可以代替NSTimer,使用比较简单.API是C语言格式的,一些读者可能不习惯 |
实现多任务定时器
有时候,会需要同时定时执行多个任务,灵活取消之前的任务,随时添加新任务,该怎么实现呢?如果感兴趣,请继续关注.笔者会在尽快实现该功能,到时候会及时更新.