NSTimer
定义
/** 这下面主要是一些构造方法*/
//1. 创建一个定时器,但是么有添加到运行循环,我们需要在创建定时器后手动的调用 NSRunLoop 对象的 addTimer:forMode: 方法。
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
//2. 创建一个timer并把它指定到一个默认的runloop模式中,并且在 TimeInterval时间后 启动定时器
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
//3. 创建一个定时器,但是么有添加到运行循环,我们需要在创建定时器后手动的调用 NSRunLoop 对象的 addTimer:forMode: 方法。
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
//3. 创建一个timer并把它指定到一个默认的runloop模式中,并且在 TimeInterval时间后 启动定时器
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
//4. 默认的初始化方法,(创建定时器后,手动添加到 运行循环,并且手动触发才会启动定时器)
- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)ti target:(id)t selector:(SEL)s userInfo:(nullable id)ui repeats:(BOOL)rep NS_DESIGNATED_INITIALIZER;
// 启动 Timer 触发Target的方法调用但是并不会改变Timer的时间设置。 即 time没有到达到,Timer会立即启动调用方法且没有改变时间设置,当时间 time 到了的时候,Timer还是会调用方法。
- (void)fire;
// 这是设置定时器的启动时间,常用来管理定时器的启动与停止
@property (copy) NSDate *fireDate;
// 启动定时器
timer.fireDate = [NSDate distantPast];
//停止定时器
timer.fireDate = [NSDate distantFuture];
// 开启
[time setFireDate:[NSDate distanPast]]
// NSTimer 关闭
[time setFireDate:[NSDate distantFunture]]
//继续。
[timer setFireDate:[NSDate date]];
// 这个是一个只读属性,获取定时器调用间隔时间
@property (readonly) NSTimeInterval timeInterval;
// 这是7.0之后新增的一个属性,因为NSTimer并不完全精准,通过这个值设置误差范围
@property NSTimeInterval tolerance NS_AVAILABLE(10_9, 7_0);
// 停止 Timer ---> 唯一的方法将定时器从循环池中移除
- (void)invalidate;
// 获取定时器是否有效
@property (readonly, getter=isValid) BOOL valid;
// 获取参数信息---> 通常传入的是 nil
@property (nullable, readonly, retain) id userInfo;
NSTimer 使用过程中的问题:
1.内存释放问题
如果我们启动了一个定时器,在某个界面释放前,将这个定时器停止,甚至置为nil,都不能使这个界面释放,原因是系统的循环池中还保有这个对象。
( timer都会对它的target进行retain,我们需要小心对待这个target的生命周期问题,尤其是重复性的timer)所以需要这么做:
-(void)dealloc{
NSLog(@"dealloc:%@",[self class]);
}
- (void)viewDidLoad {
[super viewDidLoad];
timer= [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(myLog:) userInfo:nil repeats:YES];
UIButton *btn = [[UIButton alloc]initWithFrame:CGRectMake(0, 0, 100, 100)];
btn.backgroundColor=[UIColor redColor];
[btn addTarget:self action:@selector(btn) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
}
-(void)btn{
if (timer.isValid) {
[timer invalidate]; // 从运行循环中移除, 对运行循环的引用进行一次 release
timer=nil; // 将销毁定时器
}
[self dismissViewControllerAnimated:YES completion:nil];
}
2.NSTimer为什么要添加到RunLoop中才会有作用?
便利构造器,它其实是做了两件事:
首先创建一个timer,然后将该timer添加到当前runloop的default mode中。也就是这个便利方法给我们造成了只要创建了timer就可以生效的错觉,我们当然可以自己创建timer,然后手动的把它添加到指定runloop的指定mode中去。
NSTimer其实也是一种资源(事件),如果看过多线程变成指引文档的话,我们会发现所有的source(事件)如果要起作用,就得加到runloop中去。
同理timer这种资源要想起作用,那肯定也需要加到runloop中才会有效喽。
如果一个runloop里面不包含任何资源(事件)的话,运行该runloop时会处于一种休眠状态等待下一个事件。
- 注意: 必须得把timer添加到runloop中,它才会生效。
3.NSTimer加到了RunLoop中但迟迟的不触发事件?
原因主要有两个:
- runloop是否运行?
// 每一个线程都有它自己的runloop,程序的主线程会自动的使runloop生效,但对于我们自己新建的线程,它的runloop是不会自己运行起来,当我们需要使用它的runloop时,就得自己启动。
// 打开下面一行, 该线程的runloop就会运行起来,timer才会起作用
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
- mode是否正确?
前面提到了要想timer生效,我们就得把它添加到指定runloop的指定mode中去,通常是主线程的defalut mode。但有时我们这样做了,却仍然发现timer还是没有触发事件。
这是因为timer添加的时候,我们需要指定一个mode,因为同一线程的runloop在运行的时候,任意时刻只能处于一种mode。所以只能当程序处于这种mode的时候,timer才能得到触发事件的机会。
综上: 要让timer生效,必须保证该线程的runloop已启动,而且其运行的runloopmode也要匹配。
- 注意点:
- [timer invalidate]是唯一的方法将定时器从循环池中移除
- NSTimer可以精确到50-100毫秒.
- NSTimeInterval类:是一个浮点数字,用来定义秒
- NSTimer不是绝对准确的,而且中间耗时或阻塞错过下一个点,那么下一个点就pass过去了.
CADisplayLink
什么是CADisplayLink?
- CADisplayLink是一个能让我们以和屏幕刷新率相同的频率将内容画到屏幕上的定时器。我们在应用中创建一个新的 CADisplayLink 对象,把它添加到一个runloop中,并给它提供一个 target 和selector 在屏幕刷新的时候调用。
- 一但 CADisplayLink 以特定的模式注册到runloop之后,每当屏幕需要刷新的时候,runloop就会调用CADisplayLink绑定的target上的selector,这时target可以读到 CADisplayLink 的每次调用的时间戳,用来准备下一帧显示需要的数据。例如一个视频应用使用时间戳来计算下一帧要显示的视频数据。在UI做动画的过程中,需要通过时间戳来计算UI对象在动画的下一帧要更新的大小等等。
- 在添加进runloop的时候我们应该选用高一些的优先级,来保证动画的平滑。可以设想一下,我们在动画的过程中,runloop被添加进来了一个高优先级的任务,那么,下一次的调用就会被暂停转而先去执行高优先级的任务,然后在接着执行CADisplayLink的调用,从而造成动画过程的卡顿,使动画不流畅。
- duration属性提供了每帧之间的时间,也就是屏幕每次刷新之间的的时间。我们可以使用这个时间来计算出下一帧要显示的UI的数值。但是 duration只是个大概的时间,如果CPU忙于其它计算,就没法保证以相同的频率执行屏幕的绘制操作,这样会跳过几次调用回调方法的机会。
- frameInterval属性是可读可写的NSInteger型值,标识间隔多少帧调用一次selector 方法,默认值是1,即每帧都调用一次。如果每帧都调用一次的话,对于iOS设备来说那刷新频率就是60HZ也就是每秒60次,如果将 frameInterval 设为2 那么就会两帧调用一次,也就是变成了每秒刷新30次。
- 我们通过pause属性开控制CADisplayLink的运行。当我们想结束一个CADisplayLink的时候,应该调用-(void)invalidate从runloop中删除并删除之前绑定的 target跟selector
- 另外CADisplayLink 不能被继承
CADisplayLink 与 NSTimer 有什么不同?
- iOS设备的屏幕刷新频率是固定的,CADisplayLink在正常情况下会在每次刷新结束都被调用,精确度相当高。
- NSTimer的精确度就显得低了点,比如NSTimer的触发时间到的时候,runloop如果在阻塞状态,触发时间就会推迟到下一个runloop周期。并且 NSTimer新增了tolerance属性,让用户可以设置可以容忍的触发的时间的延迟范围。
- CADisplayLink使用场合相对专一,适合做UI的不停重绘,比如自定义动画引擎或者视频播放的渲染。NSTimer的使用范围要广泛的多,各种需要单次或者循环定时处理的任务都可以使用。在UI相关的动画或者显示内容使用 CADisplayLink比起用NSTimer的好处就是我们不需要在格外关心屏幕的刷新频率了,因为它本身就是跟屏幕刷新同步的。
CADisplayLink使用的例子
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateTextColor)];
self.displayLink.paused = YES;
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
-(void)updateTextColor{}
- (void)startAnimation{
self.beginTime = CACurrentMediaTime();
self.displayLink.paused = NO;
}
- (void)stopAnimation{
self.displayLink.paused = YES;
[self.displayLink invalidate];
self.displayLink = nil;
}
- 注意:iOS设备的刷新频率事60HZ也就是每秒60次。那么每一次刷新的时间就是1/60秒 大概16.7毫秒。当我们的frameInterval值为1的时候我们需要保证的是 CADisplayLink调用的`target`的函数计算时间不应该大于 16.7否则就会出现严重的丢帧现象。
GCD定时器
- 定义
/** 定时器(这里不用带*,因为dispatch_source_t就是个类,内部已经包含了*) 这是一个OC对象 */
@property (nonatomic, strong) dispatch_source_t timer;
- 创建
// 获得队列
dispatch_queue_t queue = dispatch_get_main_queue();
// 创建一个定时器(dispatch_source_t本质还是个OC对象)
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
- 设置参数
// 设置定时器的各种属性(几时开始任务,每隔多长时间执行一次)
// GCD的时间参数,一般是纳秒(1秒 == 10的9次方纳秒)
// 何时开始执行第一个任务
// dispatch_time(DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC) 比当前时间晚3秒
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
uint64_t interval = (uint64_t)(1.0 * NSEC_PER_SEC);
dispatch_source_set_timer(self.timer, start, interval, 0);
- 设置回调
// 设置回调
dispatch_source_set_event_handler(self.timer, ^{
NSLog(@"------------%@", [NSThread currentThread]);
count++;
// if (count == 4) {
// // 取消定时器
// dispatch_source_cancel(self.timer);
// self.timer = nil;
// }
});
- 启动定时器
dispatch_resume(self.timer);
- 注意: GCD的定时器和NSTimer是不一样的,NSTimer受RunLoop影响,但是GCD的定时器不受影响,因为RunLoop也是基于GCD的(源代码可知)。
- 注:资料多是自己网上摘抄整理的,记录下来以便学习查询之用,严格原创出处不便考证以注明,若有侵权实属无意,敬请见谅! 不严谨! 条件所限,很多知识点现在还吃不透,日后发现错误再做修改。