前言
在iOS开发中,大家肯定用过不少定时器,NSTimer
是我们开发中用的最多的,当然,说起精度更好的,那肯定是GCDTimer
了,那么GCDTimer
为什么比NSTimer
更准确?它的原理和应用是怎样的?我根据我查阅的资料整理如下,如有错误望指正。
一、什么是GCD
要了解GCDTimer
,首先需要知道GCD,GCD全称 Grand Central Dispatch,是苹果公司开发的一种多线程技术,在Mac OS X 10.6 和 iOS4 中引入使用,我们可以用它快速简易地编写并发代码。
GCD 最基本的概念有队列、任务以及任务的执行方式:
- 队列:一个存放任务的缓冲区,用于管理任务的执行。有串行队列和并发队列这两种
- 任务:是需要执行的代码块
- 任务执行方式:有同步或异步2种,同步任务需要等待前一个任务执行完成后再执行,异步任务会则不需要等待前面的任务,立即返回并在后台执行(如果指定是主队列,则会在主线程执行)。
二、什么是GCDTimer
GCDTimer
是GCD中的一个定时器实现。它使用了GCD中的 Dispatch Source 技术(Dispatch Source 是 GCD 中的一种对象,可以用于异步地监视各种系统事件),通过GCD这个API创建一个定时器对象,通过指定定时器的执行队列和回调函数,让定时器在指定的时间间隔内重复执行处理代码。
GCDTimer
是内核级别的定时器,比NSTimer
更准确,对应的系统开销也更少。而且GCDTimer
利用的是GCD的线程池和任务调度,能做到多核并发,比NSTimer
具有更好的性能和效率,也不会像NSTimer
那样受主线程 RunLoop 的堵塞而受到影响,能够更好地满足应用程序的性能需求,但是使用前最好对GCD的基本概念和使用方法有一定的了解。
三、GCDTimer的参数介绍以及使用方式
GCDTimer
需要使用到的相关函数有
1.创建定时器源:dispatch_source_create()
dispatch_source_t
dispatch_source_create(dispatch_source_type_t type,
uintptr_t handle,
unsigned long mask,
dispatch_queue_t _Nullable queue);
dispatch_source_create()
函数返回dispatch_source_t
,这是一个Dispatch Source对象,Dispatch Source 是 GCD 中的一种对象,可以用于监视底层系统事件的发生并在事件发生时执行相应的处理代码。dispatch_source_create()
函数的参数分别表示:
type
:表示要创建的Dispatch Source对象的类型,本文是监视定时器的触发,因此传入的值应为DISPATCH_SOURCE_TYPE_TIMER
handle
:表示事件源的句柄(用来标识对象或者项目的标识符),可以用于将事件源绑定到指定的队列或端口上,一般填0,表示将事件源绑定到默认的全局队列中mask
:表示事件源的掩码,用于指定事件源的行为,一般填0,表示不需要设置额外的行为参数
2.设置定时器的时间间隔:dispatch_source_set_timer()
void
dispatch_source_set_timer(dispatch_source_t source,
dispatch_time_t start,
uint64_t interval,
uint64_t leeway);
source
:表示要配置的定时器的Dispatch Source对象start
:表示定时器的起始时间,即从什么时候开始触发定时器,是一个dispatch_time_t类型的值,可以使用
start:表示定时器的起始时间。它是一个dispatch_time_t类型的值,表示从什么时刻开始触发定时器。可以使用dispatch_time
函数来创建这个值interval
:表示定时器的触发时间间leeway
:表示定时器容忍的误差范围,即定时器触发的时间可以提前或延后多久,单位是纳秒,通常用来调节性能和精度之间的平衡,一般填0,表示不允许任何偏差
3.启动定时器:dispatch_resume()
void
dispatch_resume(dispatch_object_t object);
object
:表示需要被启动的 GCD 对象,可以是队列、定时器、信号源等
4.定时器触发:dispatch_source_set_event_handler()
void
dispatch_source_set_event_handler(dispatch_source_t source,
dispatch_block_t _Nullable handler);
source
:表示事件源对象,用作定时器时,传入dispatch_source_create()
函数所创建好的timer对象handler
:一个dispatch_block_t
类型的代码块,当事件源被触发时会回调到这里,可以在这里编写要执行的任务代码
5. 停止定时器:dispatch_source_cancel()
void
dispatch_source_cancel(dispatch_source_t source);
6. 暂停定时器:dispatch_suspend
void
dispatch_suspend(dispatch_object_t object);
列举一个GCDTimer
的使用例子
// 创建一个 Dispatch Source 监视定时器事件
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
// 设置定时器事件的触发时间和间隔为1s,马上触发无须延迟
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0);
// 设置定时器事件的回调函数
dispatch_source_set_event_handler(timer, ^{
NSLog(@"Event handle here.");
});
// 启动定时器事件的监视
dispatch_resume(timer);
// 暂停定时器
dispatch_suspend(timer);
// 取消定时器
dispatch_source_cancel(timer);
四、封装GCDTimer给小伙伴们使用
在平时开发中,相信很多开发小伙伴在写定时器的时候,一旦用到NSTimer
,都时不时要踩一下各种各样的坑,例如要注意释放,以免内存泄漏,要注意加入的mode是啥,否则滑动不计时,要注意不能加到子线程,因为子线程默认不创建RunLoop,导致NSTimer失效等等等等。而这些问题,GCDTimer
都通通没有!!!那么为啥一到自己开发总是不由自主地写起NSTimer
呢?我猜最大的原因是因为方便,的确写起NSTimer
是我们熟悉的oc语言,不用深入那么底层去看c语言的代码,那么为啥我们就不能自己写一个类,来另其他小伙伴更快更简单的使用计时器,又不怕踩NSTimer
的坑呢?废话少说,直接展示我的代码
GCDTimer.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface GCDTimer : NSObject
+ (GCDTimer *)scheduleTimer:(NSTimeInterval)anInterval
actionBlock:(void(^)(void))anActionBlock
willRepeat:(BOOL)willRepeat;
+ (GCDTimer *)scheduleTimer:(NSTimeInterval)anInterval
actionBlock:(void(^)(void))anActionBlock
willRepeat:(BOOL)willRepeat
dispatchQueue:(dispatch_queue_t)aDispatchQueue;
- (void)start:(NSTimeInterval)anInterval;
- (void)start;
- (void)stop;
- (void)suspend;
- (BOOL)isValid;
@end
NS_ASSUME_NONNULL_END
GCDTimer.m
#import "GCDTimer.h"
@interface GCDTimer()
@property (nonatomic, strong) dispatch_source_t dispatchTimer;//计时器
@property (nonatomic, assign) NSTimeInterval interval;//时间间隔
@property (nonatomic, assign) BOOL isSuspended;//是否暂停
@property (nonatomic, assign) BOOL willRepeat;//是否循环
@end
@implementation GCDTimer
+ (GCDTimer *)scheduleTimer:(NSTimeInterval)anInterval
actionBlock:(void(^)(void))anActionBlock
willRepeat:(BOOL)willRepeat
{
GCDTimer *timer = [[GCDTimer alloc] initWithInterval:anInterval
actionBlock:anActionBlock
willRepeat:willRepeat];
[timer start];
return timer;
}
+ (GCDTimer *)scheduleTimer:(NSTimeInterval)anInterval
actionBlock:(void(^)(void))anActionBlock
willRepeat:(BOOL)willRepeat
dispatchQueue:(dispatch_queue_t)aDispatchQueue
{
GCDTimer *timer = [[GCDTimer alloc] initWithInterval:anInterval
actionBlock:anActionBlock
willRepeat:willRepeat
dispatchQueue:aDispatchQueue];
[timer start];
return timer;
}
- (void)start:(NSTimeInterval)anInterval
{
if (NO == self.isValid || NO == self.isSuspended) {
return;
}
self.interval = anInterval;
[self start];
}
- (void)start
{
if (NO == self.isValid || NO == self.isSuspended) {
return;
}
self.isSuspended = NO;
uint64_t anInterval = self.interval * NSEC_PER_SEC;
dispatch_time_t aStartTime = dispatch_time(DISPATCH_TIME_NOW,
anInterval);
if (YES == self.willRepeat) {
dispatch_source_set_timer(self.dispatchTimer,
aStartTime, anInterval, 0);
} else {
dispatch_source_set_timer(self.dispatchTimer,
aStartTime, DISPATCH_TIME_FOREVER, 0);
}
dispatch_resume(self.dispatchTimer);
}
- (void)stop
{
if (YES == self.isValid) {
dispatch_source_cancel(self.dispatchTimer);
}
}
- (void)suspend
{
if (YES == self.isValid && NO == self.isSuspended) {
dispatch_suspend(self.dispatchTimer);
self.isSuspended = YES;
}
}
- (BOOL)isValid
{
return (nil != self.dispatchTimer
&& 0 == dispatch_source_testcancel(self.dispatchTimer));
}
#pragma mark - initialization
- (id)init
{
if (self = [super init]) {
_dispatchTimer = nil;
_isSuspended = NO;
_willRepeat = NO;
_interval = 0;
}
return self;
}
- (id)initWithInterval:(NSTimeInterval)anInterval
actionBlock:(void(^)(void))anActionBlock
willRepeat:(BOOL)willRepeat
{
return [self initWithInterval:anInterval
actionBlock:anActionBlock
willRepeat:willRepeat
dispatchQueue:dispatch_get_main_queue()];
}
- (id)initWithInterval:(NSTimeInterval)anInterval
actionBlock:(void(^)(void))anActionBlock
willRepeat:(BOOL)willRepeat
dispatchQueue:(dispatch_queue_t)aDispatchQueue
{
if (self = [super init]) {
[self initDispatchTimer:anActionBlock
willRepeat:willRepeat
dispatchQueue:aDispatchQueue];
_isSuspended = YES;
_willRepeat = willRepeat;
_interval = anInterval;
}
return self;
}
- (void)initDispatchTimer:(void(^)(void))anActionBlock
willRepeat:(BOOL)willRepeat
dispatchQueue:(dispatch_queue_t)aDispatchQueue
{
_dispatchTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,
0, 0, aDispatchQueue);
dispatch_source_t aDispatchTimer = _dispatchTimer;
dispatch_source_set_timer(aDispatchTimer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(aDispatchTimer, ^{
if (NO == willRepeat) {
dispatch_source_cancel(aDispatchTimer);
}
if (anActionBlock) {
anActionBlock();
};
});
}
- (void)dealloc
{
if (self.isValid && self.isSuspended) {
dispatch_resume(self.dispatchTimer);
}
[self stop];
}
@end
使用方式如下:
#import "ViewController.h"
#import "GCDTimer.h"
@interface ViewController ()
@property (nonatomic, strong) GCDTimer *timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self startTimer];
}
- (void)dealloc
{
[self stopTimer];
}
- (void)startTimer
{
if (self.timer) {
return;
}
//开启定时器
__weak __typeof(self) weak_self = self;
self.timer = [GCDTimer scheduleTimer:1 actionBlock:^{
[weak_self handleEvent];
} willRepeat:YES];
}
- (void)stopTimer
{
if (!self.timer) {
return;
}
[self.timer stop];
self.timer = nil;
}
- (void)handleEvent
{
//要执行的事情
}
@end
五、总结
本文介绍了GCDTimer
的原理、API以及如何封装方便使用,需要注意的是GCDTimer
是基于 Grand Central Dispatch 的实现方式,因此只适用于 iOS 和 macOS 等支持 GCD 的系统。同时,GCDTimer
的精度和性能可能会受到系统负载、电池状态等因素的影响,需要根据实际情况进行评估和优化。