iOS | GCDTimer 解读和应用

前言

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

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MY027m69' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片