前语

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 的精度和性能可能会遭到体系负载、电池状况等因素的影响,需求依据实际情况进行评估和优化。