NSTimer及一些补充(CADisplayLink)


前面的部分是翻译的官方文档, 不当之处还请指出,
后一部分则是对NSTimer的扩充, 及相关的CADisplayLink


简介

使用NSTimer能在给一个定的时间后发送一个消息给target
一般NSTimer结合runloop工作, 所以为了能够正确地使用NSTimer, 有必要学习一下NSRunLoop 和 Threading Programming Guide(PS: 大致了解一下就行). 特别注意:

run loops maintain strong references to their timers, so you don’t have to maintain your own strong reference to a timer after you have added it to a run loop

也就是说 NSTimer创建时target的是强引用, 容易和控制器造成保留环(后面会重点了解)

但是, 一个timer并不是一个严格的时间机制, 他的触发与所加入的RunLoop和RunLoop Mode有关,如果此RunLoop正在执行一个连续性的运算,timer就会被延时出发。重复性的timer遇到这种情况,如果延迟超过了一个周期,则会在延时结束后立刻执行,并按照之前指定的周期继续执行, 为了避免这种延时的发生一般设置时间间隔定时器仅限于在50 - 100毫秒之间.
如果一个timer的触发时间发生在很长一段调出或在runloop模式(不是监视计时器)的话, 该计时器直到下一个runloop才会触发, 因此一个timer的触发时间可能会比预期的时间延时.
这点也会在Timer Tolerance这节中见到.


重复 VS 不重复 计时器

non-repeating timer: 不重复计时器, 指一个计时器创建后只执行一次就invalidates销毁
repeating timer: 重复计时器, 指一个计时器创建执行一次后又重新放到相同的run loop中继续执行, 直到invalidates;


时间容差(iOS7.0 and later)

为了避免触发时间被错过, 你可以设置一个时间容差, 他可以根据系统的运行情况让timer的触发发生在规定的触发时间和(规定的触发时间+时间容差tolerance), 特别注意: 对于重复计时器来说, 他的下一次触发的时间是基于原始的触发时间, 而不会把tolerance计算在内的;
时间容差的默认值是0;
如果不设置的话, 或为0, 系统会自动设置一个很小的tolerance来优化性能,
一般来说, 对于一个重复计时器, 设置一个大于等于interval * 10%的的tolerance是比较合理的;


在runloop中设定计时器

尽管一个timer可以被添加到多个runloop内循环, 但是建议一个timer只放到一个runloop中;
有3中方法创建一个timer:
1. scheduledTimerWithTimeInterval:invocation:repeats:
scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:
创建的计时器会被放到当前的runloop中 使用的是 default mode, 而不用手动放到runloop中
2. timerWithTimeInterval:invocation:repeats:
timerWithTimeInterval:target:selector:userInfo:repeats:
创建的计时器需要手动放到runloop中(使用 addTimer:forMode:)
3. initWithFireDate:interval:target:selector:userInfo:repeats:
创建的计时器需要手动放到runloop中(使用 addTimer:forMode:)

一个timer一旦放到runloop中, timer就会在规定的时间时触发, 直到timer失效. 一个不重复的timer被触发后会被立即invalidates, 不用在手动调用[timer invalidate]这个方法; 而对于一个重复timer来说, 你需要调用[timer invalidate]方法才会销毁, 从当前的runloop中移除;
总的来说, 当一个计时器不在使用时应当及时销毁, 防止影响当前runloop, 也防止保留环的出现
一个timer一旦invalidate销毁, 就不能再被使用.

注意点: 如果一个重复的timer调用完一个方法后, 超过了下一个触发时间点, 那么被超过的这个触发时间点不在触发, 而是直接跳到下一个触发点, 也就是说, 计时器不会试图弥补任何错过的指定的时间点调用指定的选择器或调用.
比如说, timer的action是- (void)test; TimeInterval是1s, 但是这个方法执行就需要1.5秒, 那么第二秒就会被舍弃, 不再执行, 直接执行第三秒
具体看事例:

- (void)viewDidLoad {
    [super viewDidLoad];

    timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(test) userInfo:nil repeats:YES];

}

- (void)test {

    sleep(1);
    NSDate *date = [NSDate date];
    NSDateFormatter *format = [[NSDateFormatter alloc] init];
    format.dateFormat = @"HH:mm:ss.SSS";
    self.showLable.text = [format stringFromDate:date];
    NSLog(@"--%@--", self.showLable.text);

}
输出结果:
2016-01-11 17:23:59.647 TestTimerStopAndStart[12208:299855] --17:23:59.647--
2016-01-11 17:24:03.646 TestTimerStopAndStart[12208:299855] --17:24:03.646--
2016-01-11 17:24:05.646 TestTimerStopAndStart[12208:299855] --17:24:05.646--

可见中间都是差了一秒的, 也就是说本来应该被触发的那一秒没被触发, 跳过了;


创建定时器

1. + scheduledTimerWithTimeInterval:invocation:repeats:

创建一个timer并添加到当前的runloop中,
@param seconds : 如果设置的秒数小于等于0.0, 那么系统默认使用+0.1毫秒代替
@param invocation: 当timer被触发时调用, invocation的参数都是强引用, timer被销毁时invocation也销毁
@param repeats: 是否重复, YES重复, NO不重复

2. + scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:

创建一个timer并添加到当前的runloop中,
@param seconds : 如果设置的秒数小于等于0.0, 那么系统默认使用+0.1毫秒代替
@param target: timer向target发送消息调用aselector方法, timer一直对target执行强引用直到timer销毁;
@param aselector: 发送给target的消息,
@param userInfo: timer携带的信息, 也是请引用, 可以为nil
@param repeats: 是否重复, YES重复, NO不重复

3. + timerWithTimeInterval:invocation:repeats:

创建一个timer 但不会自动添加到runloop中,
@param seconds : 如果设置的秒数小于等于0.0, 那么系统默认使用+0.1毫秒代替
@param invocation: 当timer被触发时调用, invocation的参数都是强引用, timer被销毁时invocation也销毁
@param repeats: 是否重复, YES重复, NO不重复
使用此方法, 必须手动添加到通过addTimer:forMode:得到的runloop中
如果timer错过了触发点, 则会调用invocation, 如果定时器配置重复,没有必要随后重新添加计时器到runloop中

4. + timerWithTimeInterval:target:selector:userInfo:repeats:

创建一个timer 但不会自动添加到runloop中,
@param seconds : 如果设置的秒数小于等于0.0, 那么系统默认使用+0.1毫秒代替
@param target: timer向target发送消息调用aselector方法, timer一直对target执行强引用直到timer销毁;
@param aselector: 发送给target的消息,
@param userInfo: timer携带的信息, 也是请引用, 可以为nil
@param repeats: 是否重复, YES重复, NO不重复
使用此方法, 必须手动添加到通过addTimer:forMode:得到的runloop中

NSTimer *timer = [NSTimer timerWithTimeInterval:5 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];

[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

5. - initWithFireDate:interval:target:selector:userInfo:repeats:

创建一个timer 但不会自动添加到runloop中,
@param date :timer第一次被触发时间
@param seconds : 如果设置的秒数小于等于0.0, 那么系统默认使用+0.1毫秒代替
@param target: timer向target发送消息调用aselector方法, timer一直对target执行强引用直到timer销毁;
@param aselector: 发送给target的消息,
@param userInfo: timer携带的信息, 也是请引用, 可以为nil
@param repeats: 是否重复, YES重复, NO不重复
使用此方法, 必须手动添加到通过addTimer:forMode:得到的runloop中


启动定时器

fire
触发timer
如果是重复的timer, 他不会打断正常的触发
如果是不重复的timer, 则会提前触发timer, 即使还没到计划的时间


停止定时器

invalidate
是唯一个从runloop中移除计时器的方法, NSRunloop移除对timer的强引用, 并且timer的参数也移除对他参数的移除


对计时器的控制

[timer setFireDate:[NSDate date]]; //继续。
[timer setFireDate:[NSDate distantPast]];//开启
[timer setFireDate:[NSDate distantFuture]];//暂停

NSTimer 分类

///计时器 暂停
- (void)pauseTimer;

///计时器 恢复
- (void)resumeTimer;

///计时器 先暂停, 再间隔n秒后恢复计时
- (void)resumeTimerAfterTimeInterval:(NSTimeInterval)interval;
@implementation NSTimer (Addition)

- (void)pauseTimer {
    if (![self isValid]) {
        return;
    }
    [self setFireDate:[NSDate distantFuture]];
}

- (void)resumeTimer {
    if (![self isValid]) {
        return;
    }
    [self setFireDate:[NSDate date]];
    //或者[self setFireDate:[NSDate distantPast]];
}

- (void)resumeTimerAfterTimeInterval:(NSTimeInterval)interval {
    if (![self isValid]) {
        return ;
    }
    [self setFireDate:[NSDate dateWithTimeIntervalSinceNow:interval]];

}


使用NSTimer总会遇到被错过的触发点, 另一个关于时间的类是CADisplayLink

创建

self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)];    

[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

停止

self.displayLink invalidate];  

self.displayLink = nil;
/**当把CADisplayLink对象add到runloop中后,selector就能被周期性调用,类似于重复的NSTimer被启动了;执行invalidate操作时,CADisplayLink对象就会从runloop中移除,selector调用也随即停止,类似于NSTimer的invalidate方法。**/

特点

1. 屏幕刷新时调用

CADisplayLink是一个能让我们以和屏幕刷新率同步的频率将特定的内容画到屏幕上的定时器类。CADisplayLink以特定模式注册到runloop后,每当屏幕显示内容刷新结束的时候,runloop就会向CADisplayLink指定的target发送一次指定的selector消息, CADisplayLink类对应的selector就会被调用一次。所以通常情况下,按照iOS设备屏幕的刷新率60次/秒

2. 延迟

iOS设备的屏幕刷新频率是固定的,CADisplayLink在正常情况下会在每次刷新结束都被调用,精确度相当高。但如果调用的方法比较耗时,超过了屏幕刷新周期,就会导致跳过若干次回调调用机会。
如果CPU过于繁忙,无法保证屏幕60次/秒的刷新率,就会导致跳过若干次调用回调方法的机会,跳过次数取决CPU的忙碌程度。

3. 使用

从原理上可以看出,CADisplayLink适合做界面的不停重绘,比如视频播放的时候需要不停地获取下一帧用于界面渲染。

4. 重要属性

frameInterval
NSInteger类型的值,用来设置间隔多少帧调用一次selector方法,默认值是1,即每帧都调用一次。

duration
readOnly的CFTimeInterval值,表示两次屏幕刷新之间的时间间隔。需要注意的是,该属性在target的selector被首次调用以后才会被赋值。selector的调用间隔时间计算方式是:调用间隔时间 = duration × frameInterval。


特别注意:
当tableView的每个cell都有一个timer, 滑动时会卡顿, 是因为 RunLoop 的 Mode 原因,你把 timer 加入到 Runloop 中的 NSRunLoopCommonMode 就可以了。
就是这一句:[[NSRunLoop mainRunLoop] addTimer:timer forMode: NSRunLoopCommonMode];


参考:
1. https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSTimer_Class/
2. http://www.jianshu.com/p/21d351116587

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值