iOS三种定时器

在iOS开发中定时器的实现总共有三种方式:NSTimerCADisplayLinkGCD.

NSTimer

NSTimer使我们经常用到一种定时器,是一个能在从现在开始的后面的某一个时刻或者周期性的执行定时器对象。

NSTimer的创建有以下六种方法:

//普通方式
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(action) userInfo:nil repeats:YES];
//Block的方式(ios 10.0后)
[NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {

}];
//NSInvocation的方式(不常用)
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:[[self class] instanceMethodSignatureForSelector:@selector(action)]];
[invocation setTarget:self];
[invocation setSelector:action];
[NSTimer scheduledTimerWithTimeInterval:1.0 invocation:invocation repeats:YES];

userInfo:可以写一些标识信息,target : 需要执行方法的对象,selector : 需要执行的方法,repeats : 是否需要循环(当为YES的时候可以重复执行,直到手动销毁,NO的时候只执行一次),NSInvocationMethodSignature需要传入类对象

这三种方式创建的定时器不用加入到RunLoop,是由于系统已经将其加入到了默认的RunLoop中,而且这三种定时器创建后就会启动

//普通方式
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(action) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
//Block方式(ios 10.0后)
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
}];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
//NSInvocation的方式(不常用)
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:[[self class] instanceMethodSignatureForSelector:@selector(action)]];
[invocation setTarget:self];
[invocation setSelector:action];
NSTimer* timer = [NSTimer timerWithTimeInterval:1.0 invocation:invocation repeats:YES];
[[NSRunLoop mainRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode];

其中RunLoopMode常用的有三种模式:

  • NSDefaultRunLoopMode (默认模式中几乎包含了所有输入源(NSConnection除外),一般情况下应使用此模式)

  • UITrackingRunLoopMode (在拖动loop或其他user interface tracking loops时处于此种模式下,在此模式下会限制输入事件的处理。如:当手指按住UITableView拖动时就会处于此模式)

  • NSRunLoopCommonModes (这是一个伪模式,其为一组run loop mode的集合,将输入源加入此模式意味着在Common Modes中包含的所有模式下都可以处理,默认情况下Common Modes包含default modes,modal ,event Tracking modes。轮播与UITableView组合时经常用到)

这种三种方式创建的定时器都需要我们自己去加入到RunLoop中,不然定时器无法启动

停止定时器的方法:
[timer setFireDate:[NSDate distantFuture]];
开始定时器的方法:
[timer setFireDate:[NSDate distantPast]];

因为加入到了RunLoop中,创建的定时器需要我们手动释放在合适的时机调用:

[timer invalidate]; (通常在viewDidDisappear调用)

NSTimer并不是准时触发的,如果你添加了一个timer指定2秒后触发某一个事件,但是正好那个时候当前线程在执行一个连续运算(例如大数据块的处理等),这个时候timer就会延迟到该连续运算执行完以后才会执行。如果延迟超过了一个周期,则会和后面的触发进行合并,即在一个周期内只会触发一次

CADisplaylink 是一个计时器对象,可以使用这个对象来保持应用中的绘制与显示刷新的同步,要让屏幕显示的内容变化,需要以一定的频率刷新这些像素点的颜色值,系统会在每次刷新时触发 CADisplaylink,iOS设备的刷新频率事60HZ也就是每秒60次

CADisplayLink的创建:

CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(action)];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

target : 需要执行方法的对象,selector : 需要执行的方法

停止定时器的方法:
[displayLink setPaused:YES];
开始定时器的方法:
[displayLink setPaused:NO];

CADisplayLink同样需要手动释放:

[displayLink invalidate];(通常在viewDidDisappear调用)

默认情况下CADisplayLink每刷新一次(一帧),调用一次 selector,iOS设备的刷新频率事60HZ也就是每秒60次。那么每一次刷新的时间就是1/60秒 大概16.7毫秒 CADisplayLink拥有以下属性:

  • timestamp (屏幕显示的上一帧的时间戳,只读)
  • duration (每帧之间的间隔,只读)
  • frameInterval (设置调用一次selector方法之间的间隔帧数,读写,默认为1帧) 我们可以通过 CADisplayLink 对象的属性 durationframeIntervaltimestamp 获取帧率和时间信息,也可以通过设置 frameInterval 来定时触发

CADisplayLink每当屏幕刷新一次就会调用一次selector,是精确触发,但是,因为屏幕刷新并不是一成不变的每秒60帧,所以每秒调用固定次数的说法并不正确

GCD

GCD可以通过两种方式dispatch_after与dispatch_source_set_timer实现定时器

//只调用一次,通过`delayInSeconds`传入时间,单位为秒
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

});
//dispatch_source_set_timer方式
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_source_t timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//设置时间允许0纳秒偏差
dispatch_source_set_timer(timerSource, DISPATCH_TIME_NOW, 1*NSEC_PER_SEC, 0);
__block NSInteger time = 10;
dispatch_source_set_event_handler(timerSource, ^{
if (time<=0) {
//取消方法
dispatch_source_cancel(timerSource);
}else{
NSLog(@"%ld",time);
time = time-1;
dispatch_async(dispatch_get_main_ queue(), ^{
//回到主线程做事情
});
}
});

dispatch_source_set_cancel_handler(timerSource, ^{
NSLog(@"结束");
});
//执行方法,否则不会运行
dispatch_resume(timerSource);

上面为每秒触发一次的定时器,10秒之后停止

//停止的方法:
dispatch_source_cancel(timerSource);
//执行方法:
dispatch_resume(timerSource);

GCD方式实现的定时器是基于分排队,而不是像定时器一样基于运行循环,所以它不需要加到RunLoop中,其中dispatch_after只能执行一次,而且是在你设定时间将任务添加进去的方式,使用纳秒计算的方式也是为了机器需要

NSTimer可以满足大多数的需求,CADisplayLink更偏重于绘图,GCD方式写法更复杂,但却足够精确,选取哪种方式实现可以根据需求实现