美文网首页
iOS GCD定时器的封装

iOS GCD定时器的封装

作者: 里克尔梅西 | 来源:发表于2021-09-14 19:54 被阅读0次

    iOS中有三种定时器,NSTimer、CADisplayLink以及GCD。因为NSTimer和CADisplayLink都是依赖于Runloop的,所以如果Runloop任务多,可能会导致计时的不准确,所以通常情况下我们建议使用GCD的定时器

    特点
    • GCD定时器不依赖NSRunLoop,他是一个基于苹果内核的独立体系
    • 精度高,最小到1纳秒
    • 没有invalidate方法
    • 不需要手动管理内存(这里封装的target方式一定要注意循环引用)
    一般写法
    @interface ZXKGCDTimerVC ()
    @property (nonatomic, strong) dispatch_source_t gcd_timer;
    @end
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        NSTimeInterval start = 0.0;//开始时间
        NSTimeInterval interval = 1.0;//时间间隔
        /*
        这里需要一个队列
         */
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
        /*
         第一个参数:定时器对象
         第二个参数:DISPATCH_TIME_NOW 表示从现在开始计时,start * NSEC_PER_SEC表示从现在开始经过start秒执行
         第三个参数:间隔时间 GCD里面的时间最小单位为 interval * NSEC_PER_SEC表示间隔为interval秒
         第四个参数:精准度(表示允许的误差,0表示绝对精准)
         */
        dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC), interval * NSEC_PER_SEC, 0);
        dispatch_source_set_event_handler(timer, ^{
            NSLog(@"------");
        });
        self.gcd_timer = timer;
        dispatch_resume(self.gcd_timer);
    }
    - (void)dealloc {
        NSLog(@"%s", __func__);
        dispatch_source_cancel(self.gcd_timer);
    }
    
    封装

    GCD的定时器写法相对比较固定,这里建议封装,封装之前先理一理思路(GCD定时器的封装必然依赖原有的写法,所以原有的一些关键性的参数必须保留):
    1、开始时间
    2、时间间隔
    3、是否需要重复执行
    4、是否支持多线程
    5、具体执行任务的函数或者block,其实这里两种都是可以的
    6、取消定时任务
    上述就是简单的思路,接下来先进行简单的封装,如下所示:

    • 初步封装
    @interface ZXKGCDTimer : NSObject
    
    + (void)timerTask:(void(^)(void))task
                start:(NSTimeInterval) start
             interval:(NSTimeInterval) interval
              repeats:(BOOL) repeats
                 async:(BOOL)async;
    
    + (void)canelTimer;
    
    @end
    
    @implementation ZXKGCDTimer
    
    + (void)timerTask:(void(^)(void))task
                  start:(NSTimeInterval) start
               interval:(NSTimeInterval) interval
                repeats:(BOOL) repeats
                   async:(BOOL)async{
        
        /**
         对参数做一些限制
         1.如果task不存在,那就没有执行的必要(!task)
         2.开始时间必须大于当前时间
         3.当需要重复执行时,重复间隔时间必须 >0
         以上条件必须满足,定时器才算是比较合理,否则没必要执行
         */
        if (!task || start < 0 || (interval <= 0 && repeats)) {
            
            return;
        }
        //if (!task || start < 0 || (interval <= 0 && repeats)) return; (上面的代码有人可能会写成这样,都一样,这是if的语法,里面只有一行时候可以省略{},其他的没区别)
        
        /**
         队列
         async:YES 全局队列 dispatch_get_global_queue(0, 0) 可以简单理解为其他线程(非主线程)
         async:NO 主队列 dispatch_get_main_queue() 可以理解为主线程
         */
        dispatch_queue_t queue = async ? dispatch_get_global_queue(0, 0) : dispatch_get_main_queue();
    
        /**
         创建定时器 dispatch_source_t timer
         */
        dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
        dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC), interval * NSEC_PER_SEC, 0);
        dispatch_source_set_event_handler(timer, ^{
            //定时任务
            task();
            //如果不需要重复,执行一次即可
            if (!repeats) {
                
                dispatch_source_cancel(timer);
            }
        });
        //启动定时器
        dispatch_resume(timer);
    }
    
    + (void)canelTimer {
        
    }
    
    @end
    
    • 进一步封装
      当我们想取消定时器的时候会思考,多个定时器如何取消呢(很多时候可能会创建不止一个定时器),得需要一个标准,或者说根据一个标记取到对应的定时器,这里我们根据key - value的形式进行完善上述定时器,具体如下:
    @implementation ZXKGCDTimer
    
    // 用来存放多个计时器的字典
    static NSMutableDictionary *timers_;
    
    /**
     load 与 initialize区别,这里选用initialize
     */
    +(void)initialize{
        
        //GCD一次性函数
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            timers_ = [NSMutableDictionary dictionary];
            semaphore_ = dispatch_semaphore_create(1);
        });
    }
    
    + (NSString*)timerTask:(void(^)(void))task
                start:(NSTimeInterval) start
             interval:(NSTimeInterval) interval
              repeats:(BOOL) repeats
                 async:(BOOL)async{
        
        /**
         对参数做一些限制
         1.如果task不存在,那就没有执行的必要(!task)
         2.开始时间必须大于当前时间
         3.当需要重复执行时,重复间隔时间必须 >0
         以上条件必须满足,定时器才算是比较合理,否则没必要执行
         */
        if (!task || start < 0 || (interval <= 0 && repeats)) {
            
            return nil;
        }
        //if (!task || start < 0 || (interval <= 0 && repeats)) return nil; (上面的代码有人可能会写成这样,都一样,这是if的语法,里面只有一行时候可以省略{},其他的没区别)
        
        /**
         队列
         async:YES 全局队列 dispatch_get_global_queue(0, 0) 可以简单理解为其他线程(非主线程)
         async:NO 主队列 dispatch_get_main_queue() 可以理解为主线程
         */
        dispatch_queue_t queue = async ? dispatch_get_global_queue(0, 0) : dispatch_get_main_queue();
    
        /**
         创建定时器 dispatch_source_t timer
         */
        dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
        
        // 定时器的唯一标识
        NSString *timerName = [NSString stringWithFormat:@"%zd", timers_.count];
        // 存放到字典中
        timers_[timerName] = timer;
        
        dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC), interval * NSEC_PER_SEC, 0);
        dispatch_source_set_event_handler(timer, ^{
            //定时任务
            task();
            //如果不需要重复,执行一次即可
            if (!repeats) {
                
                [self canelTimer:timerName];
            }
        });
        //启动定时器
        dispatch_resume(timer);
        
        return timerName;
    }
    
    +(void)canelTimer:(NSString*) timerName{
        
        if (timerName.length == 0) {
            
            return;
        }
        
        dispatch_source_t timer = timers_[timerName];
        if (timer) {
            
            dispatch_source_cancel(timer);
            [timers_ removeObjectForKey:timerName];
        }
    }
    
    @end
    
    • 最终封装
      上面的代码涉及到对字典的存取,所以还需要考虑到线程安全(只需要在存值取值的时候进行处理),因此,需要进一步完善代码,此外我们还加入了暂停和继续的方法,特别注意,suspend必须与resume成对存在,不然就会发生崩溃,而且我们不只是以block的方式进行封装,当然了,也可以使用target的方式,相对比较简单,具体如下:
    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface ZXKGCDTimer : NSObject
    
    /**
     Block方式的定时器
     
     @param task 任务(这里使用block)
     @param start 开始时间
     @param interval 间隔
     @param repeats 时候否重复调用
     @param async 同步异步
     @return 定时器标识(最终取消定时器是需要根据此标识取消的)
     */
    + (NSString *)timerTask:(void(^)(void))task
                      start:(NSTimeInterval)start
                   interval:(NSTimeInterval)interval
                    repeats:(BOOL)repeats
                      async:(BOOL)async;
    
    /**
     Target方式的定时器
     
     @param target 目标对象(这里使用方法)
     @param selector 调用方法
     @param start 开始时间
     @param interval 间隔
     @param repeats 是否重复调用
     @param async 同步异步
     @return 定时器标识(最终取消定时器是需要根据此标识取消的)
     */
    + (NSString *)timerTask:(id)target
                   selector:(SEL)selector
                      start:(NSTimeInterval)start
                   interval:(NSTimeInterval)interval
                    repeats:(BOOL)repeats
                      async:(BOOL)async;
    
    /**
     取消定时器
     
     @param timerName 定时器标识
     */
    + (void)canelTimer:(NSString *)timerName;
    
    /**
     暂停定时器
     
     @param timerName 定时器标识
     */
    + (void)pauseTimer:(NSString *)timerName;
    
    /**
     继续定时器
     
     @param timerName 定时器标识
     */
    + (void)continueTimer:(NSString *)timerName;
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    
    #import "ZXKGCDTimer.h"
    
    @implementation ZXKGCDTimer
    
    //全局字典,存放timer对象
    static NSMutableDictionary *timers_;
    //信号量,对字典的操作进行加锁操作
    dispatch_semaphore_t semaphore_;
    
    + (void)initialize {
        //GCD一次性函数
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            timers_ = [NSMutableDictionary dictionary];
            semaphore_ = dispatch_semaphore_create(1);
        });
    }
    
    + (NSString *)timerTask:(void(^)(void))task
                      start:(NSTimeInterval)start
                   interval:(NSTimeInterval)interval
                    repeats:(BOOL) repeats
                      async:(BOOL)async{
        
        /**
         对参数做一些限制
         1.如果task不存在,那就没有执行的必要(!task)
         2.开始时间必须大于当前时间
         3.当需要重复执行时,重复间隔时间必须 >0
         以上条件必须满足,定时器才算是比较合理,否则没必要执行
         */
        if (!task || start < 0 || (interval <= 0 && repeats)) {
            return nil;
        }
        
        /**
         队列
         async:YES 异步 全局队列 dispatch_get_global_queue(0, 0) 可以简单理解为其他线程(非主线程)
         async:NO  同步 dispatch_get_main_queue() 可以理解为主线程
         */
        dispatch_queue_t queue = async ? dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) : dispatch_get_main_queue();
        
        /**
         创建定时器 dispatch_source_t timer
         */
        dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
        
        // 信号量加锁
        dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
        // 定时器的唯一标识
        NSString *timerName = [NSString stringWithFormat:@"%zd", timers_.count];
        // 存放到字典中
        timers_[timerName] = timer;
        dispatch_semaphore_signal(semaphore_);
        
        dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC), interval * NSEC_PER_SEC, 0);
        dispatch_source_set_event_handler(timer, ^{
            //定时任务
            task();
            //如果不需要重复,执行一次即可
            if (!repeats) {
                [self canelTimer:timerName];
            }
        });
        //启动定时器
        dispatch_resume(timer);
        
        return timerName;
    }
    
    + (NSString*)timerTask:(id)target
                  selector:(SEL)selector
                     start:(NSTimeInterval)start
                  interval:(NSTimeInterval)interval
                   repeats:(BOOL)repeats
                     async:(BOOL)async{
        
        if (!target || !selector) return nil;
        
        return [self timerTask:^{
            
            if ([target respondsToSelector:selector]) {
                //(这是消除警告的处理)
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
                [target performSelector:selector];
    #pragma clang diagnostic pop
            }
            
        } start:start interval:interval repeats:repeats async:async];
    }
    
    + (void)canelTimer:(NSString *)timerName {
        if (timerName.length == 0) {
            return;
        }
        
        // 信号量加锁
        dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
        
        dispatch_source_t timer = timers_[timerName];
        if (timer) {
            dispatch_source_cancel(timer);
            [timers_ removeObjectForKey:timerName];
        }
        
        dispatch_semaphore_signal(semaphore_);
    }
    
    + (void)pauseTimer:(NSString *)timerName {
        if (timerName.length == 0) {
            return;
        }
            
        dispatch_source_t timer = timers_[timerName];
        if (timer) {
            dispatch_suspend(timer);
        }
    }
    
    + (void)continueTimer:(NSString *)timerName {
        if (timerName.length == 0) {
            return;
        }
            
        dispatch_source_t timer = timers_[timerName];
        if (timer) {
            dispatch_resume(timer);
        }
    }
    
    @end
    
    • 最终使用
      包括了block和target两种样式
    #import "ZXKGCDTimerVC.h"
    
    @interface ZXKGCDTimerVC ()
    
    @property (nonatomic, strong) NSString *gcdTimer;
    @property (nonatomic, assign) int count;
    @end
    
    @implementation ZXKGCDTimerVC
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.count = 100;
        
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(didBecomeAction)
                                                     name:UIApplicationDidBecomeActiveNotification
                                                   object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(willResignAction)
                                                     name:UIApplicationWillResignActiveNotification
                                                   object:nil];
        __weak typeof(self) weakSelf = self;
        self.gcdTimer = [ZXKGCDTimer timerTask:^{
            [weakSelf timerAction];
        } start:0 interval:1 repeats:YES async:YES];
        
    //    self.gcdTimer = [ZXKGCDTimer timerTask:[ZXKProxy proxyWithTarget:self] selector:@selector(timerAction) start:0 interval:1 repeats:YES async:YES];
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        [ZXKGCDTimer canelTimer:self.gcdTimer];
    }
    
    - (void)timerAction {
        NSLog(@"每秒执行--%d", self.count);
        self.count--;
    }
    
    
    - (IBAction)pauseAction:(id)sender {
        [ZXKGCDTimer pauseTimer:self.gcdTimer];
    }
    
    - (IBAction)continueAction:(id)sender {
        [ZXKGCDTimer continueTimer:self.gcdTimer];
    }
    
    - (void)didBecomeAction {
        NSLog(@"进入前台");
    }
    
    - (void)willResignAction {
        NSLog(@"退到后台");
    }
    
    - (void)dealloc {
        NSLog(@"%s", __func__);
        [ZXKGCDTimer canelTimer:self.gcdTimer];
        [[NSNotificationCenter defaultCenter] removeObserver:self];
    }
    

    运行结果如下图:


    image.png

    结束语

    到这里,GCD定时器相关的内容基本上告一段落,提供了2套GCD的封装,这里建议使用block方式。今后在项目中使用的时候就使用GCD形式的定时器了

    相关文章

      网友评论

          本文标题:iOS GCD定时器的封装

          本文链接:https://www.haomeiwen.com/subject/nvzcgltx.html