NSTimer
概述
NSTimer是Foundation框架中的一个类,用于创建定时器,可以在指定的时间间隔内执行指定的任务。NSTimer以RunLoop的方式工作,可以将其加入RunLoop的特定模式中,从而定时器可以在指定的时间间隔内持续工作
调用方式
方式1
// 以该方式创建的定时器以默认方式添加到当前线程runloop中,无需手动添加
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"%s, %d", __func__, __LINE__);
}];
// 之所以指定该定时器所在的Mode为NSRunLoopCommonModes,是为了保证定时器可以在不同的RunLoop Mode下正常运行
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
// 停止定时器
[timer invalidate];
方式2
// 以该方式创建的定时器需要手动添加到RunLoop才能正常执行
NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(test:) userInfo:@"2" repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
-(void)test: (NSTimer *)content {
NSLog(@"%s, %d, %@", __func__, __LINE__, [content userInfo]);
}
// 停止定时器
[timer invalidate];
使用陷阱
NSTimer的工作方式是基于RunLoop的,因此需要将其加入RunLoop才能生效。如果不加入RunLoop,定时器将无法启动- 在使用
NSTimer时要注意循环引用问题,应该使用弱引用或者block来避免循环引用,也可通过NSProxy创建一个临时Target - 在多线程中使用
NSTimer,需要注意线程安全的问题,可以考虑使用GCD定时器或者NSRunLoop的performSelector:afterDelay:方法等代替 - 当定时器不再使用时,需要将其从
RunLoop中移除,避免内存泄漏和无用功耗,且其创建与撤销必须在同一个线程操作,不能跨越线程操作 NSTimer依赖于Runloop,如果Runloop的任务过于繁重,可能会导致NStimer不准时
CADisplayLink
概述
CADisplayLink是Core Animation框架中的一个类,用于创建一个与显示器刷新频率相同的计时器,如每秒 60 次(屏幕刷新率为 60Hz)调用一次指定方法。CADisplayLink可以直接和Core Animation进行结合,用于实现动画渲染、图形绘制等操作,而且可以在多个RunLoop Modes中执行,非常灵活。
使用场景
- 实时数据展示:比如在股票行情中,实时绘制股票价格图表
- 游戏开发:比如用于精灵动画的连续播放
- 视频录制:实时处理视频帧数据,实现视频的录制和处理
- 音频波形分析:实时绘制音频波形图,显示音频数据变化
调用方式
// 需要将CADisplayLink对象添加到runLoop中,selector就能被周期性调用
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(test:)];
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
使用陷阱
CADisplayLink的任务方法需要在RunLoop中运行,因此需要将其加入指定的RunLoop Mode中CADisplayLink显示器刷新率为 60Hz,因此它的任务方法最好不要执行过于耗时的操作,以避免影响界面流畅性- 在使用
CADisplayLink时要注意循环引用问题,应该使用弱引用或者block来避免循环引用 - 当
CADisplayLink不再使用时,需要手动停止它的工作,避免内存泄漏和无用功耗
GCD
+ (NSString *)executeTask:(task)task
start:(NSTimeInterval)start
interval:(NSTimeInterval)interval
repeats:(BOOL)repeats
async:(BOOL)async {
if (!task || start < 0 || (interval <= 0 && repeats) ) return nil;
// 队列
dispatch_queue_t queue = async ? dispatch_get_global_queue(0, 0) : dispatch_get_main_queue();
// 创建定时器
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 设置时间
// 精准度:一般为0,如果对定时操作要求不精确,只要在一段时间内执行就可以,那么可以把精准度设置的大一点,例如10,这样会提高程序性能
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, interval * NSEC_PER_SEC, 0);
// 加锁
dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
// 定时器唯一标识
NSString *name = [NSString stringWithFormat:@"%ld", timers_.count];
// 存放到字典中
timers_[name] = timer;
// 解锁
dispatch_semaphore_signal(semaphore_);
// 设置回调
dispatch_source_set_event_handler(timer, ^{
task();
// 不重复的任务
if (!repeats) {
[self cancelTask:name];
}
});
// 启动定时器
dispatch_resume(timer);
return name;
}
优点
GCD定时器实际上是使用了dispatch源(dispatch source),dispatch源监听系统内核对象并处理。dispatch类似生产者消费者模式,通过监听系统内核对象,在生产者生产数据后自动通知相应的dispatch队列执行,后者充当消费者。通过系统级调用,更加精准